基于Springboot搭建个人博客 (学习笔记)

个人博客搭建

  • 技术选型
    • 主要框架
  • 安装 RabbitMq,Elasticsearch
    • 安装RabbitMq
      • 安装环境
      • 下载安装包
      • 安装配置 erlang
      • 安装配置 rabbitMQ
      • 验证
    • 安装Elasticsearch
      • 下载地址
  • 创建Springboot 项目
    • pom.xml
    • 前端的框架用的是 layui 的 fly-3.0
    • 拆分首页 以及填充
    • 引入Mybatis-plus
    • 处理分页的问题
    • 处理显示的时间问题,渲染时间
      • 错误记录
    • 修改置顶部分
    • 详情页面修改
    • 修改提问的页面
    • 处理评论问题
    • 本周热议功能
    • 文章的浏览量功能
    • 集成Shiro完成登录注册功能
    • 修改登录之后导航栏显示自己信息以及退出登录
    • 基本设置功能
      • 个人主页问题
      • 基本设置功能
      • 上传头像的功能
      • 修改密码的功能
    • 用户中心发表的贴和收藏的贴
    • 消息中心
  • 下面的内容地址

技术选型

主要框架

核心框架 Springboot
安全框架 Apache Shiro
持久层框架 Mybatis + mybatis-plus
页面模板 Freemarker
缓存框架 Redis
数据库 mysql
消息队列 RabbitMq
分布式搜索 Elasticsearch
双工通讯协议:websocket
网络通讯框架:t-io
工具集合:hutool

安装 RabbitMq,Elasticsearch

安装RabbitMq

安装环境

系统:win10 64位专业版
erlang:otp_win64_24.0
rabbitMQ:rabbitmq-server-3.8.19
安装rabbitMQ需要依赖erlang语言环境,所以需要我们下载erlang的环境安装程序。

下载安装包

rabbitMQ安装程序下载路径:https://www.rabbitmq.com/install-windows-manual.html
基于Springboot搭建个人博客 (学习笔记)_第1张图片

![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/fcfdfb6e7ca013aca108e203e2d2f8b4.png

erlang环境安装程序下载路径: https://www.erlang.org/downloads
基于Springboot搭建个人博客 (学习笔记)_第2张图片

安装配置 erlang

对于安装路径没有特殊要求的话,就一路next直至安装成功即可,默认安装路径为:C:\Program Files\erl-23.0。

接下来配置环境变量,常规操作,新建系统变量-键入变量名ERLANG_HOME,键入变量值:erlang安装路径。如下图:

然后添加系统path路径中,添加 : %ERLANG_HOME%\bin

基于Springboot搭建个人博客 (学习笔记)_第3张图片
然后打开cmd,输入erl,看到我们的erlang版本号,就说明安装成功了

基于Springboot搭建个人博客 (学习笔记)_第4张图片

安装配置 rabbitMQ

双击我们刚才下载的rabbitmq-server-3.8.19程序,next,install即可,此处需要注意,如果要自定义安装路径的话,路径中最好不要存在中文,会出现错误。
安装完成之后,需要我们激活rabbitmq_management

打开cmd,进到sbin目录下,运行命令

rabbitmq-plugins enable rabbitmq_management

基于Springboot搭建个人博客 (学习笔记)_第5张图片

验证

上面的命令执行成功之后,我们就可以通过http://localhost:15672来访问web端的管理界面
初始可以通过用户名:guest 密码guest来登录

基于Springboot搭建个人博客 (学习笔记)_第6张图片
这就说明我们安装成功了。

net start RabbitMQ  #启动
net stop RabbitMQ  #停止
rabbitmqctl status  #查看状态

安装Elasticsearch

下载地址

https://www.elastic.co/cn/elasticsearch/ 官网
基于Springboot搭建个人博客 (学习笔记)_第7张图片
基于Springboot搭建个人博客 (学习笔记)_第8张图片
下载完成后
基于Springboot搭建个人博客 (学习笔记)_第9张图片
目录结构
bin :启动文件
config 配置文件
log4j2 :日志文件
jvm.options java 虚拟机相关的配置
elasticsearch.yml elasticsearch 的配置文件
lib 相关jar 包
logs 日志
modules 功能模块
plugins 插件 ik

在bin 里点击启动即可
基于Springboot搭建个人博客 (学习笔记)_第10张图片
基于Springboot搭建个人博客 (学习笔记)_第11张图片
安装head 插件
在 github 下载 https://github.com/mobz/elasticsearch-head/
简单的前端页面
安装 cnpm install
基于Springboot搭建个人博客 (学习笔记)_第12张图片
基于Springboot搭建个人博客 (学习笔记)_第13张图片
**此时还不能链接上我们的 es **
基于Springboot搭建个人博客 (学习笔记)_第14张图片
连接不上解决一下跨域的问题
在ElasticSearch.yml 加上这两句话
http.cors.enabled: true
http.cors.allow-origin: “*”

此时就可以连接上来了
基于Springboot搭建个人博客 (学习笔记)_第15张图片

创建Springboot 项目

这个操作比较简单

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.1.2.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.examplegroupId>
    <artifactId>Springboot-blogartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>Springboot-blogname>
    <description>Demo project for Spring Bootdescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-freemarkerartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.2.0version>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-generatorartifactId>
            <version>3.2.0version>
        dependency>

        
        <dependency>
            <groupId>p6spygroupId>
            <artifactId>p6spyartifactId>
            <version>3.8.6version>
        dependency>

        
        <dependency>
            <groupId>org.apache.commonsgroupId>
            <artifactId>commons-lang3artifactId>
            <version>3.9version>
        dependency>

        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>4.1.17version>
        dependency>

        
        <dependency>
            <groupId>com.github.axetgroupId>
            <artifactId>kaptchaartifactId>
            <version>0.0.9version>
        dependency>

        
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-springartifactId>
            <version>1.4.0version>
        dependency>

        <dependency>
            <groupId>net.mingsoftgroupId>
            <artifactId>shiro-freemarker-tagsartifactId>
            <version>0.1version>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-websocketartifactId>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-elasticsearchartifactId>
            <version>2.1.1.RELEASEversion>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>

        <dependency>
            <groupId>org.modelmappergroupId>
            <artifactId>modelmapperartifactId>
            <version>1.1.0version>
        dependency>

        
        <dependency>
            <groupId>org.t-iogroupId>
            <artifactId>tio-websocket-serverartifactId>
            <version>3.2.5.v20190101-RELEASEversion>
        dependency>

    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

前端的框架用的是 layui 的 fly-3.0

我放在了 资源里面 可以下载到https://download.csdn.net/download/m0_46937429/20398937?spm=1001.2014.3001.5503
基于Springboot搭建个人博客 (学习笔记)_第16张图片
大概框架是这个样子.下载在我的资源里面

拆分首页 以及填充

先把 下载好的 fly 3.0 的首页引入到 templates
controller 层

package com.example.springbootblog.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class IndexController {
    @RequestMapping ({"","/","index"})
    public String index () {
        return "index";
    }
}

基于Springboot搭建个人博客 (学习笔记)_第17张图片

基于Springboot搭建个人博客 (学习笔记)_第18张图片

此时访问 http://localhost:8080/
基于Springboot搭建个人博客 (学习笔记)_第19张图片
我们可以把 头信息抽离出来 再用 freemarker 引入就可以了
基于Springboot搭建个人博客 (学习笔记)_第20张图片
基于Springboot搭建个人博客 (学习笔记)_第21张图片
然后在 index.ftl 里面引入就可以了
基于Springboot搭建个人博客 (学习笔记)_第22张图片
同样的道理把 导航的部分跟 尾部抽离出来就可以了
中间部分处理
基于Springboot搭建个人博客 (学习笔记)_第23张图片

可以先把右边的部分抽离出来
基于Springboot搭建个人博客 (学习笔记)_第24张图片
这个 right.ftl 里面就是 那个 md 4 的 同样的道理 把 左侧的可以 抽离出来

这个时候还不太够完善,有好多的地方可以在所有的地方会用到
**新建一个 layout.ftl **
基于Springboot搭建个人博客 (学习笔记)_第25张图片

用这个标签 暂时把 index.ftl 里面的东西 复制到 下面这个标签 里面
但是不能全部复制过来 比如 这几个 就不是公共的。就是首页自己的。可能别的页面就没有的

<#macro layout title>

</macro>

基于Springboot搭建个人博客 (学习笔记)_第26张图片

怎么引用我们写的layout.ftl 页面呢?

<#include "./inc/layout.ftl"/>
<#-- 导入 layout 的标签 -->
<@layout "首页" >
<#include "./inc/hrader-panel.ftl"/>
<div class="layui-container">
    <div class="layui-row layui-col-space15">

        <#include "./inc/left.ftl"/>
        <#include "./inc/right.ftl"/>
    </div>
</div>
</@layout>

运行访问 ,其实就是拼图 套娃。哈哈哈哈
基于Springboot搭建个人博客 (学习笔记)_第27张图片
还可以访问,证明我们的抽离没有问题
添加 页面
基于Springboot搭建个人博客 (学习笔记)_第28张图片
基于Springboot搭建个人博客 (学习笔记)_第29张图片
把这两个页面复制过来

先写Controller 测试一下能不能用

@Controller
public class PostController {
    // 指定值接收 数字类型
    @GetMapping("/category/{id:\\d*}")
    public String category(@PathVariable(name = "id") Long id) {
        return "post/category";
    }
    @GetMapping("/detail/{id:\\d*}")
    public String detail(@PathVariable(name = "id") Long id) {
        return "post/detail";
    }
}

效果
基于Springboot搭建个人博客 (学习笔记)_第30张图片
基于Springboot搭建个人博客 (学习笔记)_第31张图片
是没有问题的。但是不能用 Controller 形式 跳转。我们应该用 页面的形式 。接下来修改页面用模板进行跳转
category 页面


<#include "../inc/layout.ftl"/>
<#-- 导入 layout 的标签 -->
<@layout "博客分类" >

  <#include "../inc/hrader-panel.ftl"/>

<div class="layui-container">
  <div class="layui-row layui-col-space15">
    <div class="layui-col-md8">
      <div class="fly-panel" style="margin-bottom: 0;">

        <div class="fly-panel-title fly-filter">
          <a href="" class="layui-this">综合</a>
          <span class="fly-mid"></span>
          <a href="">未结</a>
          <span class="fly-mid"></span>
          <a href="">已结</a>
          <span class="fly-mid"></span>
          <a href="">精华</a>
          <span class="fly-filter-right layui-hide-xs">
            <a href="" class="layui-this">按最新</a>
            <span class="fly-mid"></span>
            <a href="">按热议</a>
          </span>
        </div>

        <ul class="fly-list">
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">分享</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--
                <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
                -->
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <!--<span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>-->
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <span class="layui-badge layui-bg-black">置顶</span>
              <!--<span class="layui-badge layui-bg-red">精帖</span>-->
            </div>
          </li>
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">动态</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--<i class="iconfont icon-renzheng" title="认证信息:XXX"></i>-->
                <i class="layui-badge fly-badge-vip">VIP3</i>
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <span class="layui-badge layui-bg-red">精帖</span>
            </div>
          </li>
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">动态</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--
                <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
                -->
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <!--<span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>-->
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <!--<span class="layui-badge layui-bg-red">精帖</span>-->
            </div>
          </li>
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">动态</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--
                <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
                -->
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <!--<span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>-->
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <!--<span class="layui-badge layui-bg-red">精帖</span>-->
            </div>
          </li>
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">动态</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--
                <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
                -->
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <!--<span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>-->
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <!--<span class="layui-badge layui-bg-red">精帖</span>-->
            </div>
          </li>
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">动态</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--
                <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
                -->
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <!--<span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>-->
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <!--<span class="layui-badge layui-bg-red">精帖</span>-->
            </div>
          </li>
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">动态</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--
                <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
                -->
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <!--<span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>-->
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <!--<span class="layui-badge layui-bg-red">精帖</span>-->
            </div>
          </li>
          <li>
            <a href="user/home.html" class="fly-avatar">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <h2>
              <a class="layui-badge">动态</a>
              <a href="detail.html">基于 layui 的极简社区页面模版</a>
            </h2>
            <div class="fly-list-info">
              <a href="user/home.html" link>
                <cite>贤心</cite>
                <!--
                <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
                -->
              </a>
              <span>刚刚</span>

              <span class="fly-list-kiss layui-hide-xs" title="悬赏飞吻"><i class="iconfont icon-kiss"></i> 60</span>
              <!--<span class="layui-badge fly-badge-accept layui-hide-xs">已结</span>-->
              <span class="fly-list-nums">
                <i class="iconfont icon-pinglun1" title="回答"></i> 66
              </span>
            </div>
            <div class="fly-list-badge">
              <!--<span class="layui-badge layui-bg-red">精帖</span>-->
            </div>
          </li>
        </ul>

        <!-- <div class="fly-none">没有相关数据</div> -->

        <div style="text-align: center">
          <div class="laypage-main"><span class="laypage-curr">1</span><a href="/jie/page/2/">2</a><a href="/jie/page/3/">3</a><a href="/jie/page/4/">4</a><a href="/jie/page/5/">5</a><span></span><a href="/jie/page/148/" class="laypage-last" title="尾页">尾页</a><a href="/jie/page/2/" class="laypage-next">下一页</a></div>
        </div>

      </div>
    </div>
    <#include "../inc/right.ftl">
  </div>
</div>
</@layout>

同理把detail.ftl 页面修改一下

<#include "../inc/layout.ftl"/>
<#-- 导入 layout 的标签 -->
<@layout "博客分类" >

  <#include "../inc/hrader-panel.ftl"/>



  <div class="layui-container">
    <div class="layui-row layui-col-space15">
      <div class="layui-col-md8 content detail">
        <div class="fly-panel detail-box">
          <h1>Fly Template v3.0,基于 layui 的极简社区页面模版</h1>
          <div class="fly-detail-info">
            <!-- <span class="layui-badge">审核中</span> -->
            <span class="layui-badge layui-bg-green fly-detail-column">动态</span>

            <span class="layui-badge" style="background-color: #999;">未结</span>
            <!-- <span class="layui-badge" style="background-color: #5FB878;">已结</span> -->

            <span class="layui-badge layui-bg-black">置顶</span>
            <span class="layui-badge layui-bg-red">精帖</span>

            <div class="fly-admin-box" data-id="123">
              <span class="layui-btn layui-btn-xs jie-admin" type="del">删除</span>

              <span class="layui-btn layui-btn-xs jie-admin" type="set" field="stick" rank="1">置顶</span>
              <!-- <span class="layui-btn layui-btn-xs jie-admin" type="set" field="stick" rank="0" style="background-color:#ccc;">取消置顶</span> -->

              <span class="layui-btn layui-btn-xs jie-admin" type="set" field="status" rank="1">加精</span>
              <!-- <span class="layui-btn layui-btn-xs jie-admin" type="set" field="status" rank="0" style="background-color:#ccc;">取消加精</span> -->
            </div>
            <span class="fly-list-nums">
            <a href="#comment"><i class="iconfont" title="回答">&#xe60c;</i> 66</a>
            <i class="iconfont" title="人气">&#xe60b;</i> 99999
          </span>
          </div>
          <div class="detail-about">
            <a class="fly-avatar" href="../user/home.html">
              <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt="贤心">
            </a>
            <div class="fly-detail-user">
              <a href="../user/home.html" class="fly-link">
                <cite>贤心</cite>
                <i class="iconfont icon-renzheng" title="认证信息:{{ rows.user.approve }}"></i>
                <i class="layui-badge fly-badge-vip">VIP3</i>
              </a>
              <span>2017-11-30</span>
            </div>
            <div class="detail-hits" id="LAY_jieAdmin" data-id="123">
              <span style="padding-right: 10px; color: #FF7200">悬赏:60飞吻</span>
              <span class="layui-btn layui-btn-xs jie-admin" type="edit"><a href="add.html">编辑此贴</a></span>
            </div>
          </div>
          <div class="detail-body photos">
            <p>
              该模版由 layui官方社区(<a href="http://fly.layui.com/" target="_blank">fly.layui.com</a>)倾情提供,只为表明我们对 layui 执着的信念、以及对未来持续加强的承诺。该模版基于 layui 搭建而成,可作为极简通用型社区的页面支撑。
            </p>
            <p>更新日志:</p>
            <pre>
# v3.0 2017-11-30
* 采用 layui 2.2.3 作为 UI 支撑
* 全面同步最新的 Fly 社区风格,各种细节得到大幅优化
* 更友好的响应式适配能力
</pre>

            下载<hr>
            <p>
              官网:<a href="http://www.layui.com/template/fly/" target="_blank">http://www.layui.com/template/fly/</a><br>
              码云:<a href="https://gitee.com/sentsin/fly/" target="_blank">https://gitee.com/sentsin/fly/</a><br>
              GitHub:<a href="https://github.com/layui/fly" target="_blank">https://github.com/layui/fly</a>
            </p>
            封面<hr>
            <p>
              <img src="../../res/images/fly.jpg" alt="Fly社区">
            </p>
          </div>
        </div>

        <div class="fly-panel detail-box" id="flyReply">
          <fieldset class="layui-elem-field layui-field-title" style="text-align: center;">
            <legend>回帖</legend>
          </fieldset>

          <ul class="jieda" id="jieda">
            <li data-id="111" class="jieda-daan">
              <a name="item-1111111111"></a>
              <div class="detail-about detail-about-reply">
                <a class="fly-avatar" href="">
                  <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt=" ">
                </a>
                <div class="fly-detail-user">
                  <a href="" class="fly-link">
                    <cite>贤心</cite>
                    <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                    <i class="layui-badge fly-badge-vip">VIP3</i>
                  </a>

                  <span>(楼主)</span>
                  <!--
                  <span style="color:#5FB878">(管理员)</span>
                  <span style="color:#FF9E3F">(社区之光)</span>
                  <span style="color:#999">(该号已被封)</span>
                  -->
                </div>

                <div class="detail-hits">
                  <span>2017-11-30</span>
                </div>

                <i class="iconfont icon-caina" title="最佳答案"></i>
              </div>
              <div class="detail-body jieda-body photos">
                <p>香菇那个蓝瘦,这是一条被采纳的回帖</p>
              </div>
              <div class="jieda-reply">
              <span class="jieda-zan zanok" type="zan">
                <i class="iconfont icon-zan"></i>
                <em>66</em>
              </span>
                <span type="reply">
                <i class="iconfont icon-svgmoban53"></i>
                回复
              </span>
                <div class="jieda-admin">
                  <span type="edit">编辑</span>
                  <span type="del">删除</span>
                  <!-- <span class="jieda-accept" type="accept">采纳</span> -->
                </div>
              </div>
            </li>

            <li data-id="111">
              <a name="item-1111111111"></a>
              <div class="detail-about detail-about-reply">
                <a class="fly-avatar" href="">
                  <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt=" ">
                </a>
                <div class="fly-detail-user">
                  <a href="" class="fly-link">
                    <cite>贤心</cite>
                  </a>
                </div>
                <div class="detail-hits">
                  <span>2017-11-30</span>
                </div>
              </div>
              <div class="detail-body jieda-body photos">
                <p>蓝瘦那个香菇,这是一条没被采纳的回帖</p>
              </div>
              <div class="jieda-reply">
              <span class="jieda-zan" type="zan">
                <i class="iconfont icon-zan"></i>
                <em>0</em>
              </span>
                <span type="reply">
                <i class="iconfont icon-svgmoban53"></i>
                回复
              </span>
                <div class="jieda-admin">
                  <span type="edit">编辑</span>
                  <span type="del">删除</span>
                  <span class="jieda-accept" type="accept">采纳</span>
                </div>
              </div>
            </li>

            <!-- 无数据时 -->
            <!-- <li class="fly-none">消灭零回复</li> -->
          </ul>

          <div class="layui-form layui-form-pane">
            <form action="/jie/reply/" method="post">
              <div class="layui-form-item layui-form-text">
                <a name="comment"></a>
                <div class="layui-input-block">
                  <textarea id="L_content" name="content" required lay-verify="required" placeholder="请输入内容"  class="layui-textarea fly-editor" style="height: 150px;"></textarea>
                </div>
              </div>
              <div class="layui-form-item">
                <input type="hidden" name="jid" value="123">
                <button class="layui-btn" lay-filter="*" lay-submit>提交回复</button>
              </div>
            </form>
          </div>
        </div>
      </div>
      <#include "../inc/right.ftl">
    </div>
  </div>
</@layout>

以上前端暂时开发完毕

引入Mybatis-plus

导入mp的依赖包

   
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.2.0version>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-generatorartifactId>
            <version>3.2.0version>
        dependency>

从官网把生成代码的代码复制下来改一下

public class 生成代码 {


    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("fjj");
        gc.setOpen(false);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/eblog?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
//        pc.setModuleName(scanner("模块名"));
        pc.setParent("com.example.springbootblog");
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };

        // 如果模板引擎是 freemarker
        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        // String templatePath = "/templates/mapper.xml.vm";

        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();


        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);

        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        // 公共父类

        // 写于父类中的公共字段

        strategy.setInclude(scanner("user").split(","));
        strategy.setControllerMappingHyphenStyle(true);

        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        mpg.execute();
    }

}


点击之后生成我们的代码和实体类注意要修改链接数据库还有包的地方
此外在引入MP 之后一定要编写COnfig 类或者在主启动类上加上 MapperScan
基于Springboot搭建个人博客 (学习笔记)_第32张图片

这样启动的时候就不会报错了
基于Springboot搭建个人博客 (学习笔记)_第33张图片
说白了就是现在上面的那些导航栏是死的,我们应该从数据库里面去查询我们的数据。变成项目启动的时候动态出来我们的数据就可以了 。

创建ContextStartup

@Component
// 实现 启动类 ,还有 上下文的servlect
public class ContextStartup implements ApplicationRunner, ServletContextAware {
// 注入 categoryService
    @Autowired
    IMCategoryService categoryService;
    ServletContext servletContext;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 调用全查的方法
        List<MCategory> list = categoryService.list(new QueryWrapper<MCategory>().eq("status", 0));
        servletContext.setAttribute("List",list);
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
this.servletContext =servletContext;
    }
}

修改
基于Springboot搭建个人博客 (学习笔记)_第34张图片
基于Springboot搭建个人博客 (学习笔记)_第35张图片
基于Springboot搭建个人博客 (学习笔记)_第36张图片
变为动态的了,但是还是有一个小的bug 就是当我们点击的时候首页那个绿色不会消失
创建一个公共的Controller 类,主要放一些公共需要继承的东西基于Springboot搭建个人博客 (学习笔记)_第37张图片

修改我们的PostController 类 设置属性
基于Springboot搭建个人博客 (学习笔记)_第38张图片
基于Springboot搭建个人博客 (学习笔记)_第39张图片
效果这个时候我们点击分享的时候首页就不会是绿色的,只有分享是绿色的
基于Springboot搭建个人博客 (学习笔记)_第40张图片

处理分页的问题

引入Mp 的分页插件
基于Springboot搭建个人博客 (学习笔记)_第41张图片
基于Springboot搭建个人博客 (学习笔记)_第42张图片
编写首页的Controller
解释为啥用这个方法

基于Springboot搭建个人博客 (学习笔记)_第43张图片
源码
基于Springboot搭建个人博客 (学习笔记)_第44张图片
Ok 接着编写
Controller 类编写完毕

@Controller
public class IndexController extends BaseController {
    @RequestMapping({"", "/", "index"})
    public String index() {
        // 设置默认的开始页面
        // 这里为啥不用 request.Parameter 获取是因为他不可以设置默认的值 这个工具类会判断你有没有设置默认页面 如果设置了就先展示
        int start = ServletRequestUtils.getIntParameter(request, "start", 1);
        // 设置默认的展示多少页面
        int end = ServletRequestUtils.getIntParameter(request, "end", 2);
//         调用 Mp 的分页方法
        Page page = new Page(start, end);
        // 调用服务类 1,分页信息 2,分类 3,用户 ,4 置顶 ,5 精选 6排序 排序是通过时间进行一个排序
       IPage results = postService.paging(page,null,null,null,null,"created");
        request.setAttribute("CategoryId", 0);
        request.setAttribute("created", 0);
        return "index";
    }
}

Postserviceimpl 实现类

/**
 * 

* 服务实现类 *

* * @author fjj * @since 2021-07-23 */
@Service public class MPostServiceImpl extends ServiceImpl<MPostMapper, MPost> implements IMPostService { @Autowired MPostMapper postMapper; @Override public IPage paging(Page page, Long categoryId, Long userId, Integer level, Boolean recommend, String order) { // 判断 level 是否等于 空 if (level == null) level = -1; // 获取wrapper QueryWrapper wrapper = new QueryWrapper<MPost>() .eq(categoryId != null,"category_id",categoryId) .eq(userId !=null ,"user_id",userId) .eq(level == 0 ,"level",0) .gt(level >0 ,"level",0) .orderByDesc(order != null,order); return postMapper.selectPosts(page, wrapper); } }

MPostMapper 的Mapper

/**
 * 

* Mapper 接口 *

* * @author fjj * @since 2021-07-23 */
@Mapper public interface MPostMapper extends BaseMapper<MPost> { // 这里之所以要 @Param(Constants.WRAPPER) 是 MP 官网的要求 IPage<PostVo> selectPosts( Page page ,@Param(Constants.WRAPPER) QueryWrapper wrapper); }

MPostMapper.xml 的Spl 语句 应该是3个表连起来


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springbootblog.mapper.MPostMapper">
<select id="selectPosts" resultType="com.example.springbootblog.vo.PostVo">
    SELECT
        p.*,

        u.id AS authorId,
        u.username AS authorName,
        u.avatar AS authorAvatar,

        c.id AS categoryId,
        c.name AS categoryName
    FROM
        m_post p
            LEFT JOIN m_user u ON p.user_id = u.id
            LEFT JOIN m_category c ON p.category_id = c.id
        ${ew.customSqlSegment}
select>
mapper>

现在我们的Sql 都还不能看到参考MP 配置一下Sql 分析打印

配置application.yml

mybatis-plus:
  mapper-locations: classpath*:/mapper/**Mapper.xml
    #用于mybatis在控制台打印sql日志
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

重启项目
基于Springboot搭建个人博客 (学习笔记)_第45张图片
这个SQL是因为在我们的Config 里面有个
在这里插入图片描述
前端页面获取
基于Springboot搭建个人博客 (学习笔记)_第46张图片
这里之所以是 pageData.records是因为返回的是我们写好的PostVo 因为是三个表查询,有的是通过id查询的别的表的字段,原来的实体类里面没有有些字段所以先建了一个PostVo.class 用来映射个别字段继承Post 的实体类这样就可以有所有的字段了

package com.example.springbootblog.vo;

import com.example.springbootblog.entity.MPost;
import lombok.Data;

@Data
public class PostVo extends MPost {
    private Long authorId;
    private String authorName;
    private String authorAvatar;
//    private Long categoryId;
    private String categoryName;
}

打印输出一下pageData.records可以看到就是映射的是我们的Postvo.class
基于Springboot搭建个人博客 (学习笔记)_第47张图片

这样的话就页面就可以访问到我们数据库的数据了
基于Springboot搭建个人博客 (学习笔记)_第48张图片
基于Springboot搭建个人博客 (学习笔记)_第49张图片
基于Springboot搭建个人博客 (学习笔记)_第50张图片
效果
基于Springboot搭建个人博客 (学习笔记)_第51张图片
虽然渲染出来了,但是不是我们想要的数据。我们应该是存我们的后台返回出来的数据进行分页
基于Springboot搭建个人博客 (学习笔记)_第52张图片
修改前端的地方
基于Springboot搭建个人博客 (学习笔记)_第53张图片

基于Springboot搭建个人博客 (学习笔记)_第54张图片
layui 官网对于分页的这几个对象属性的解释
基于Springboot搭建个人博客 (学习笔记)_第55张图片
虽然有了从数据库出来的分页的数据的效果但是当我们点击第二页的时候数据并不会变化,所以还需要在修改一下代码layui 官网上就有
基于Springboot搭建个人博客 (学习笔记)_第56张图片

复制过来
基于Springboot搭建个人博客 (学习笔记)_第57张图片
基于Springboot搭建个人博客 (学习笔记)_第58张图片
抽取出来分页,因为分页不可能只在这一个地方出现
基于Springboot搭建个人博客 (学习笔记)_第59张图片

修改index.ftl 页面
基于Springboot搭建个人博客 (学习笔记)_第60张图片
不要忘记在layout里面添加标签
基于Springboot搭建个人博客 (学习笔记)_第61张图片
至此分页功能结束

处理显示的时间问题,渲染时间

两种方法

  1. 用JS 进行渲染
  2. 用freemarker标签
    基于Springboot搭建个人博客 (学习笔记)_第62张图片

编写freemarker的接口
基于Springboot搭建个人博客 (学习笔记)_第63张图片
DirectiveHandler

package com.example.springbootblog.common.templates;

import freemarker.template.*;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import static org.apache.commons.lang3.StringUtils.*;

/**
 * Freemarker 模型工具类
 *
 * Created by langhsu on 2017/11/14.
 */
public class TemplateModelUtils {

    public static final DateFormat FULL_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static final int FULL_DATE_LENGTH = 19;

    public static final DateFormat SHORT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    public static final int SHORT_DATE_LENGTH = 10;

    public static String converString(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateScalarModel) {
                return ((TemplateScalarModel) model).getAsString();
            } else if ((model instanceof TemplateNumberModel)) {
                return ((TemplateNumberModel) model).getAsNumber().toString();
            }
        }
        return null;
    }

    public static TemplateHashModel converMap(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateHashModelEx) {
                return (TemplateHashModelEx) model;
            } else if (model instanceof TemplateHashModel) {
                return (TemplateHashModel) model;
            }
        }
        return null;
    }

    public static Integer converInteger(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().intValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Integer.parseInt(s);
                    } catch (NumberFormatException e) {
                    }
                }
            }
        }
        return null;
    }

    public static Short converShort(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().shortValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Short.parseShort(s);
                    } catch (NumberFormatException e) {
                    }
                }
            }
        }
        return null;
    }

    public static Long converLong(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().longValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Long.parseLong(s);
                    } catch (NumberFormatException e) {
                    }
                }
            }
        }
        return null;
    }

    public static Double converDouble(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().doubleValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Double.parseDouble(s);
                    } catch (NumberFormatException ignored) {
                    }
                }
            }
        }
        return null;
    }

    public static String[] converStringArray(TemplateModel model) throws TemplateModelException {
        if (model instanceof TemplateSequenceModel) {
            TemplateSequenceModel smodel = (TemplateSequenceModel) model;
            String[] values = new String[smodel.size()];
            for (int i = 0; i < smodel.size(); i++) {
                values[i] = converString(smodel.get(i));
            }
            return values;
        } else {
            String str = converString(model);
            if (isNotBlank(str)) {
                return split(str,',');
            }
        }
        return null;
    }

    public static Boolean converBoolean(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateBooleanModel) {
                return ((TemplateBooleanModel) model).getAsBoolean();
            } else if (model instanceof TemplateNumberModel) {
                return !(0 == ((TemplateNumberModel) model).getAsNumber().intValue());
            } else if (model instanceof TemplateScalarModel) {
                String temp = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(temp)) {
                    return Boolean.valueOf(temp);
                }
            }
        }
        return null;
    }

    public static Date converDate(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateDateModel) {
                return ((TemplateDateModel) model).getAsDate();
            } else if (model instanceof TemplateScalarModel) {
                String temp = trimToEmpty(((TemplateScalarModel) model).getAsString());
                return parseDate(temp);
            }
        }
        return null;
    }

    public static Date parseDate(String date) {

        Date ret = null;
        try {
            if (FULL_DATE_LENGTH == date.length()) {
                ret = FULL_DATE_FORMAT.parse(date);
            } else if (SHORT_DATE_LENGTH == date.length()) {
                ret = SHORT_DATE_FORMAT.parse(date);
            }
        } catch (ParseException e) {
        }
        return ret;
    }
}

TemplateDirective

package com.example.springbootblog.common.templates;

import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;

import java.io.IOException;
import java.util.Map;

/**
 * Created by langhsu on 2017/11/14.
 */
public abstract class TemplateDirective implements TemplateDirectiveModel {
    protected static String RESULT = "result";
    protected static String RESULTS = "results";

    @Override
    public void execute(Environment env, Map parameters,
                        TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
        try {
            execute(new DirectiveHandler(env, parameters, loopVars, body));
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new TemplateException(e, env);
        }
    }

    abstract public String getName();
    abstract public void execute(DirectiveHandler handler) throws Exception;

}

TemplateModelUtils

package com.example.springbootblog.common.templates;

import freemarker.template.*;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import static org.apache.commons.lang3.StringUtils.*;

/**
 * Freemarker 模型工具类
 *
 * Created by langhsu on 2017/11/14.
 */
public class TemplateModelUtils {

    public static final DateFormat FULL_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static final int FULL_DATE_LENGTH = 19;

    public static final DateFormat SHORT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    public static final int SHORT_DATE_LENGTH = 10;

    public static String converString(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateScalarModel) {
                return ((TemplateScalarModel) model).getAsString();
            } else if ((model instanceof TemplateNumberModel)) {
                return ((TemplateNumberModel) model).getAsNumber().toString();
            }
        }
        return null;
    }

    public static TemplateHashModel converMap(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateHashModelEx) {
                return (TemplateHashModelEx) model;
            } else if (model instanceof TemplateHashModel) {
                return (TemplateHashModel) model;
            }
        }
        return null;
    }

    public static Integer converInteger(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().intValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Integer.parseInt(s);
                    } catch (NumberFormatException e) {
                    }
                }
            }
        }
        return null;
    }

    public static Short converShort(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().shortValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Short.parseShort(s);
                    } catch (NumberFormatException e) {
                    }
                }
            }
        }
        return null;
    }

    public static Long converLong(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().longValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Long.parseLong(s);
                    } catch (NumberFormatException e) {
                    }
                }
            }
        }
        return null;
    }

    public static Double converDouble(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateNumberModel) {
                return ((TemplateNumberModel) model).getAsNumber().doubleValue();
            } else if (model instanceof TemplateScalarModel) {
                String s = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(s)) {
                    try {
                        return Double.parseDouble(s);
                    } catch (NumberFormatException ignored) {
                    }
                }
            }
        }
        return null;
    }

    public static String[] converStringArray(TemplateModel model) throws TemplateModelException {
        if (model instanceof TemplateSequenceModel) {
            TemplateSequenceModel smodel = (TemplateSequenceModel) model;
            String[] values = new String[smodel.size()];
            for (int i = 0; i < smodel.size(); i++) {
                values[i] = converString(smodel.get(i));
            }
            return values;
        } else {
            String str = converString(model);
            if (isNotBlank(str)) {
                return split(str,',');
            }
        }
        return null;
    }

    public static Boolean converBoolean(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateBooleanModel) {
                return ((TemplateBooleanModel) model).getAsBoolean();
            } else if (model instanceof TemplateNumberModel) {
                return !(0 == ((TemplateNumberModel) model).getAsNumber().intValue());
            } else if (model instanceof TemplateScalarModel) {
                String temp = ((TemplateScalarModel) model).getAsString();
                if (isNotBlank(temp)) {
                    return Boolean.valueOf(temp);
                }
            }
        }
        return null;
    }

    public static Date converDate(TemplateModel model) throws TemplateModelException {
        if (null != model) {
            if (model instanceof TemplateDateModel) {
                return ((TemplateDateModel) model).getAsDate();
            } else if (model instanceof TemplateScalarModel) {
                String temp = trimToEmpty(((TemplateScalarModel) model).getAsString());
                return parseDate(temp);
            }
        }
        return null;
    }

    public static Date parseDate(String date) {

        Date ret = null;
        try {
            if (FULL_DATE_LENGTH == date.length()) {
                ret = FULL_DATE_FORMAT.parse(date);
            } else if (SHORT_DATE_LENGTH == date.length()) {
                ret = SHORT_DATE_FORMAT.parse(date);
            }
        } catch (ParseException e) {
        }
        return ret;
    }
}

编写template继承刚的方法
基于Springboot搭建个人博客 (学习笔记)_第64张图片

package com.example.springbootblog.template;

import com.example.springbootblog.common.templates.DirectiveHandler;
import freemarker.template.TemplateModelException;
import org.springframework.stereotype.Component;

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

@Component
public class TimeAgoMethod extends DirectiveHandler.BaseMethod {
    private static final long ONE_MINUTE = 60000L;
    private static final long ONE_HOUR = 3600000L;
    private static final long ONE_DAY = 86400000L;
    private static final long ONE_WEEK = 604800000L;

    private static final String ONE_SECOND_AGO = "秒前";
    private static final String ONE_MINUTE_AGO = "分钟前";
    private static final String ONE_HOUR_AGO = "小时前";
    private static final String ONE_DAY_AGO = "天前";
    private static final String ONE_MONTH_AGO = "月前";
    private static final String ONE_YEAR_AGO = "年前";
    private static final String ONE_UNKNOWN = "未知";

    @Override
    public Object exec(List arguments) throws TemplateModelException {
        Date time = getDate(arguments, 0);
        return format(time);
    }

    public static String format(Date date) {
        if (null == date) {
            return ONE_UNKNOWN;
        }
        long delta = new Date().getTime() - date.getTime();
        if (delta < 1L * ONE_MINUTE) {
            long seconds = toSeconds(delta);
            return (seconds <= 0 ? 1 : seconds) + ONE_SECOND_AGO;
        }
        if (delta < 45L * ONE_MINUTE) {
            long minutes = toMinutes(delta);
            return (minutes <= 0 ? 1 : minutes) + ONE_MINUTE_AGO;
        }
        if (delta < 24L * ONE_HOUR) {
            long hours = toHours(delta);
            return (hours <= 0 ? 1 : hours) + ONE_HOUR_AGO;
        }
        if (delta < 48L * ONE_HOUR) {
            return "昨天";
        }
        if (delta < 30L * ONE_DAY) {
            long days = toDays(delta);
            return (days <= 0 ? 1 : days) + ONE_DAY_AGO;
        }
        if (delta < 12L * 4L * ONE_WEEK) {
            long months = toMonths(delta);
            return (months <= 0 ? 1 : months) + ONE_MONTH_AGO;
        } else {
            long years = toYears(delta);
            return (years <= 0 ? 1 : years) + ONE_YEAR_AGO;
        }
    }

    private static long toSeconds(long date) {
        return date / 1000L;
    }

    private static long toMinutes(long date) {
        return toSeconds(date) / 60L;
    }

    private static long toHours(long date) {
        return toMinutes(date) / 60L;
    }

    private static long toDays(long date) {
        return toHours(date) / 24L;
    }

    private static long toMonths(long date) {
        return toDays(date) / 30L;
    }

    private static long toYears(long date) {
        return toMonths(date) / 365L;
    }
}

编写Config


@Configuration
public class FreemarkerConfig {

    @Autowired
    private freemarker.template.Configuration configuration;

    @PostConstruct
    public void setUp() {
        configuration.setSharedVariable("timeAgo", new TimeAgoMethod());
    }

}

修改前端就可以了
基于Springboot搭建个人博客 (学习笔记)_第65张图片
效果
基于Springboot搭建个人博客 (学习笔记)_第66张图片

错误记录

刚开始的时候我的日期一直是未知 ,打断点发现前台传给后台的时候一直就是null 所以一直返回的未知
基于Springboot搭建个人博客 (学习笔记)_第67张图片
打断点调试
基于Springboot搭建个人博客 (学习笔记)_第68张图片
最后在实体类的发现用的日期格式化是
基于Springboot搭建个人博客 (学习笔记)_第69张图片
换成Date 之后
发现可以使用了
总结可能是因为我们继承的freemarker 的接口他们规定的 yyyy-MM-dd 的格式

修改置顶部分

创建自定义的标签
基于Springboot搭建个人博客 (学习笔记)_第70张图片
PostsTemplate

@Component
public class PostsTemplate extends TemplateDirective {
        // 注入服务类
    @Autowired
    IMPostService postService;

    @Override
    public String getName() {
        return "posts";
    }

    @Override
    public void execute(DirectiveHandler handler) throws Exception {
        // 设置置顶
        Integer level = handler.getInteger("level");
        //设置分页
        Integer pn = handler.getInteger("pn", 1);
        Integer size = handler.getInteger("size", 2);
        // 设置creadeID
        Long categoryId = handler.getLong("categoryId");
        IPage<PostVo> page = postService.paging(new Page(pn, size), categoryId, null, level, null, "created");
        handler.put(RESULTS,page).render();

    }
}

在FreemarkerConfig里面注入

@Configuration
public class FreemarkerConfig {

    @Autowired
    private freemarker.template.Configuration configuration;
    @Autowired
    PostsTemplate postsTemplate;
    @PostConstruct
    public void setUp() {
        configuration.setSharedVariable("timeAgo", new TimeAgoMethod());
        configuration.setSharedVariable("posts", postsTemplate);
    }


}

修改前端这里的页面
基于Springboot搭建个人博客 (学习笔记)_第71张图片
基于Springboot搭建个人博客 (学习笔记)_第72张图片
把原来的这部分代码抽离出来添加到common.ftl。方便调用
基于Springboot搭建个人博客 (学习笔记)_第73张图片
抽离出来之后
基于Springboot搭建个人博客 (学习笔记)_第74张图片
index.ftl 页面
基于Springboot搭建个人博客 (学习笔记)_第75张图片
效果
基于Springboot搭建个人博客 (学习笔记)_第76张图片数据库中置顶的也只有一篇
基于Springboot搭建个人博客 (学习笔记)_第77张图片

详情页面修改

点击标题到详情页面功能

基于Springboot搭建个人博客 (学习笔记)_第78张图片

PostController

@Controller
public class PostController extends BaseController{
    // 指定值接收 数字类型
    @GetMapping("/category/{id:\\d*}")
    public String category(@PathVariable(name = "id") Long id) {
        // 给每一个都设置一个属性
        request.setAttribute("CategoryId",id);
        return "post/category";
    }
    @GetMapping("/detail/{id:\\d*}")
    public String detail(@PathVariable(name = "id") Long id) {
        // 查询数据 后面传得到底是根据那个id 进行查询。
     PostVo postVo =  postService.selectOnePost(new QueryWrapper<MPost>().eq("p.id",id));
     //断言判断是否被删除、如果时空的话就提示文章已经被删除了
        Assert.notNull(postVo,"文章已被删除");
        // 这个是为了设置 可以方便到底是那个 是分享还是提问之类
        request.setAttribute("CategoryId",postVo.getCategoryId());
        // 这个是为了设置到前台方便获取到数据
        request.setAttribute("post",postVo);
        return "post/detail";
    }
}

postServiceimpl

    @Override
    public PostVo selectOnePost(QueryWrapper<MPost> wrapper) {
        return postMapper.selectOnePost(wrapper);
    }

PostMapper

// 这里要加上这个注解上面解释过为什么了
 PostVo selectOnePost(@Param(Constants.WRAPPER) QueryWrapper<MPost> wrapper);

PostMapper.xml


    <select id="selectOnePost" resultType="com.example.springbootblog.vo.PostVo">
        SELECT
            p.*,

            u.id AS authorId,
            u.username AS authorName,
            u.avatar AS authorAvatar,

            c.id AS categoryId,
            c.name AS categoryName
        FROM
            m_post p
                LEFT JOIN m_user u ON p.user_id = u.id
                LEFT JOIN m_category c ON p.category_id = c.id
            ${ew.customSqlSegment}
    select>

修改前端页面detail.ftl

// 
<#include "../inc/layout.ftl"/>
<#-- 导入 layout 的标签 -->
<@layout "博客分类" >

  <#include "../inc/hrader-panel.ftl"/>
  <div class="layui-container">
    <div class="layui-row layui-col-space15">
      <div class="layui-col-md8 content detail">
        <div class="fly-panel detail-box">
          <h1>${post.title}</h1>
          <div class="fly-detail-info">
            <!-- <span class="layui-badge">审核中</span> -->
            <span class="layui-badge layui-bg-green fly-detail-column">${post.categoryName}</span>


          <#if  post.level gt 0 ><span class="layui-badge layui-bg-black">置顶</span></#if>
          <#if  post.recommend><span class="layui-badge layui-bg-red">精帖</span> </#if>

            <div class="fly-admin-box" data-id="${post.id}">
              <span class="layui-btn layui-btn-xs jie-admin" type="del">删除</span>

              <span class="layui-btn layui-btn-xs jie-admin" type="set" field="stick" rank="1">置顶</span>
              <!-- <span class="layui-btn layui-btn-xs jie-admin" type="set" field="stick" rank="0" style="background-color:#ccc;">取消置顶</span> -->

              <span class="layui-btn layui-btn-xs jie-admin" type="set" field="status" rank="1">加精</span>
              <!-- <span class="layui-btn layui-btn-xs jie-admin" type="set" field="status" rank="0" style="background-color:#ccc;">取消加精</span> -->
            </div>
            <span class="fly-list-nums">
            <a href="#comment"><i class="iconfont" title="回答">&#xe60c;</i> ${post.commentCount}</a>
            <i class="iconfont" title="人气">&#xe60b;</i> ${post.viewCount}
          </span>
          </div>
          <div class="detail-about">
            <a class="fly-avatar" href="/user/${post.authorId}">
              <img src="${post.authorAvatar}" alt="${post.authorName}">
            </a>
            <div class="fly-detail-user">
              <a href="/user/${post.authorId}" class="fly-link">
              <cite>${post.authorName}</cite>
              </a>
              <span>${timeAgo(post.created)}</span>
            </div>
            <div class="detail-hits" id="LAY_jieAdmin" data-id="${post.id}">
<#--              <#if profile.id == post.userId><span class="layui-btn layui-btn-xs jie-admin" type="edit"><a href="/post/edit?id=${post.id}">编辑此贴</a></span></#if>-->
            </div>
          </div>
          <div class="detail-body photos">
            ${post.content}
          </div>
        </div>

        <div class="fly-panel detail-box" id="flyReply">
          <fieldset class="layui-elem-field layui-field-title" style="text-align: center;">
            <legend>回帖</legend>
          </fieldset>

          <ul class="jieda" id="jieda">
            <li data-id="111" class="jieda-daan">
              <a name="item-1111111111"></a>
              <div class="detail-about detail-about-reply">
                <a class="fly-avatar" href="">
                  <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt=" ">
                </a>
                <div class="fly-detail-user">
                  <a href="" class="fly-link">
                    <cite>贤心</cite>
                    <i class="iconfont icon-renzheng" title="认证信息:XXX"></i>
                    <i class="layui-badge fly-badge-vip">VIP3</i>
                  </a>

                  <span>(楼主)</span>

                </div>

                <div class="detail-hits">
                  <span>2017-11-30</span>
                </div>

                <i class="iconfont icon-caina" title="最佳答案"></i>
              </div>
              <div class="detail-body jieda-body photos">
                <p>香菇那个蓝瘦,这是一条被采纳的回帖</p>
              </div>
              <div class="jieda-reply">
              <span class="jieda-zan zanok" type="zan">
                <i class="iconfont icon-zan"></i>
                <em>66</em>
              </span>
                <span type="reply">
                <i class="iconfont icon-svgmoban53"></i>
                回复
              </span>
                <div class="jieda-admin">
                  <span type="edit">编辑</span>
                  <span type="del">删除</span>
                  <!-- <span class="jieda-accept" type="accept">采纳</span> -->
                </div>
              </div>
            </li>

            <li data-id="111">
              <a name="item-1111111111"></a>
              <div class="detail-about detail-about-reply">
                <a class="fly-avatar" href="">
                  <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" alt=" ">
                </a>
                <div class="fly-detail-user">
                  <a href="" class="fly-link">
                    <cite>贤心</cite>
                  </a>
                </div>
                <div class="detail-hits">
                  <span>2017-11-30</span>
                </div>
              </div>
              <div class="detail-body jieda-body photos">
                <p>蓝瘦那个香菇,这是一条没被采纳的回帖</p>
              </div>
              <div class="jieda-reply">
              <span class="jieda-zan" type="zan">
                <i class="iconfont icon-zan"></i>
                <em>0</em>
              </span>
                <span type="reply">
                <i class="iconfont icon-svgmoban53"></i>
                回复
              </span>
                <div class="jieda-admin">
                  <span type="edit">编辑</span>
                  <span type="del">删除</span>
                  <span class="jieda-accept" type="accept">采纳</span>
                </div>
              </div>
            </li>

            <!-- 无数据时 -->
            <!-- <li class="fly-none">消灭零回复</li> -->
          </ul>

          <div class="layui-form layui-form-pane">
            <form action="/jie/reply/" method="post">
              <div class="layui-form-item layui-form-text">
                <a name="comment"></a>
                <div class="layui-input-block">
                  <textarea id="L_content" name="content" required lay-verify="required" placeholder="请输入内容"  class="layui-textarea fly-editor" style="height: 150px;"></textarea>
                </div>
              </div>
              <div class="layui-form-item">
                <input type="hidden" name="jid" value="123">
                <button class="layui-btn" lay-filter="*" lay-submit>提交回复</button>
              </div>
            </form>
          </div>
        </div>
      </div>
      <#include "../inc/right.ftl">
    </div>
  </div>
</@layout>

效果
基于Springboot搭建个人博客 (学习笔记)_第79张图片

修改提问的页面

基于Springboot搭建个人博客 (学习笔记)_第80张图片
修改前端的页面我们可以用我们自己自定义的标签
基于Springboot搭建个人博客 (学习笔记)_第81张图片

分页的信息要在PostController 里面设置一下

public class PostController extends BaseController{
    // 指定值接收 数字类型
    @GetMapping("/category/{id:\\d*}")
    public String category(@PathVariable(name = "id") Long id) {
        int pn = ServletRequestUtils.getIntParameter(request, "start", 1);
        request.setAttribute("CategoryId", id);
        request.setAttribute("start", pn);
        return "post/category";
    }

效果
基于Springboot搭建个人博客 (学习笔记)_第82张图片

处理评论问题

基于Springboot搭建个人博客 (学习笔记)_第83张图片
处理这里的评论
编写Postcontroller类

    @GetMapping("/detail/{id:\\d*}")
    public String detail(@PathVariable(name = "id") Long id) {
        // 查询数据
     PostVo postVo =  postService.selectOnePost(new QueryWrapper<MPost>().eq("p.id",id));
     //断言判断是否被删除
        Assert.notNull(postVo,"文章已被删除");
        // 调用 评论的方法 1, 分页 2,文章的id 3, 用户 id 4,排序
      IPage<CommentVo> results = imCommentService.paging(getPage(),postVo.getId(),null,"created");
        request.setAttribute("CategoryId",postVo.getCategoryId());
        request.setAttribute("post",postVo);
        request.setAttribute("pageData",results);
        return "post/detail";
    }

评论的服务类

/**
 * 

* 服务实现类 *

* * @author fjj * @since 2021-07-23 */
@Service public class MCommentServiceImpl extends ServiceImpl<MCommentMapper, MComment> implements IMCommentService { // 导入 Mapper @Autowired MCommentMapper mCommentMapper; @Override public IPage<CommentVo> paging(Page page, Long postId, Long userid, String order) { return mCommentMapper.selectComment(page, new QueryWrapper<MComment>() .eq(postId != null, "post_id", postId) .eq(userid != null, "user_id", userid) .orderByDesc(order != null, order) ); } }

评论的mapper

@Mapper
public interface MCommentMapper extends BaseMapper<MComment> {

    IPage<CommentVo> selectComment(Page page, @Param(Constants.WRAPPER) QueryWrapper<MComment> orderByDesc);
}

评论的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.example.springbootblog.mapper.MCommentMapper">
<select id="selectComment" resultType="com.example.springbootblog.vo.CommentVo">
    SELECT
        c.*,
        u.id AS authorId,
        u.username AS authorName,
        U.avatar AS authorAvatar
    FROM
        m_comment c
            LEFT JOIN m_user u ON c.user_id = u.id
        ${ew.customSqlSegment}
</select>
</mapper>

CommentVo

@Data
public class CommentVo extends MComment {
    // 作者的ID
    private Long authorId;
    //名字
    private String authorName;
    private String authorAvatar;
}

前端修改

        <div class="fly-panel detail-box" id="flyReply">
          <fieldset class="layui-elem-field layui-field-title" style="text-align: center;">
            <legend>回帖</legend>
          </fieldset>

          <ul class="jieda" id="jieda">
            <#list pageData.records as comment>
            <li data-id="${comment.id}" class="jieda-daan">
              <a name="${comment.id}"></a>
              <div class="detail-about detail-about-reply">
                <a class="fly-avatar" href="/user/${post.authorId}">
                  <img src="${post.authorAvatar}" alt="${post.authorName}">
                </a>
                <div class="fly-detail-user">
                  <a href="/user/${post.authorId}" class="fly-link">
                    <cite>${post.authorName}</cite>
                  </a>

                  <#if comment.userId == post.userId>
                    <span>(楼主)</span>
                  </#if>
                </div>

                <div class="detail-hits">
                  <span>${timeAgo(comment.created)}</span>
                </div>

              </div>
              <div class="detail-body jieda-body photos">
                <p>${comment.content}</p>
              </div>
              <div class="jieda-reply">
              <span class="jieda-zan zanok" type="zan">
                <i class="iconfont icon-zan"></i>
                <em>${comment.voteUp}</em>
              </span>
                <span type="reply">
                <i class="iconfont icon-svgmoban53"></i>
                回复
              </span>
                <div class="jieda-admin">
                  <span type="edit">编辑</span>
                  <span type="del">删除</span>

                </div>
              </div>
            </li>
            </#list>

          </ul>
          <@paging pageData></@paging>
          <div class="layui-form layui-form-pane">
            <form action="/jie/reply/" method="post">
              <div class="layui-form-item layui-form-text">
                <a name="comment"></a>
                <div class="layui-input-block">
                  <textarea id="L_content" name="content" required lay-verify="required" placeholder="请输入内容"  class="layui-textarea fly-editor" style="height: 150px;"></textarea>
                </div>
              </div>
              <div class="layui-form-item">
                <input type="hidden" name="jid" value="${post.id}">
                <button class="layui-btn" lay-filter="*" lay-submit>提交回复</button>
              </div>
            </form>
          </div>
        </div>

效果
基于Springboot搭建个人博客 (学习笔记)_第84张图片

本周热议功能

基于Springboot搭建个人博客 (学习笔记)_第85张图片

我写在了我的另一个博客里面,可以点击查看

文章的浏览量功能

当我们刷新页面浏览量应该加1,但是如果一直刷新一直差数据库的话会对数据库造成压力,所以可以先夹缓存里面然后定时添加到数据库
先写入缓存
基于Springboot搭建个人博客 (学习笔记)_第86张图片

    @Override
    public void putViewCount(PostVo postVo) {
        // 获取到 key
        String key = "rank:post:" +postVo.getId();
        // 先从缓存中拿到我们的viewCount
        Integer viewCount = (Integer) redisUtil.hget(key, "post:viewCount");
        // 判断存在不存在,如果存在的话就加1 不存在从实体 加1
        if (viewCount!=null) {
            postVo.setViewCount(viewCount +1);
        } else {
            postVo.setViewCount(postVo.getViewCount()+1);
        }
        // 同步到缓存中
        redisUtil.hset(key,"post:viewCount",postVo.getViewCount());

    }

现在可以存到缓存里面了,设置定时的功能
基于Springboot搭建个人博客 (学习笔记)_第87张图片

@Component
public class ViewCountSyncTask {
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    IMPostService postService;
    @Autowired
    RedisTemplate redisTemplate;

    // 设置定时器的时间设置
    @Scheduled(cron = "* 1-2 0/1 * * ? ")
    public void task() {
        // 获取到所有的key
        Set<String> keys = redisTemplate.keys("rank:post:*");
        // 创建用来存放的ids
        ArrayList<String> ids = new ArrayList<>();
        // 遍历所有的key 找到 存放浏览量的 key
        for (String key : keys) {
            // 判断是否存在 redis 中
            if (redisUtil.hHasKey(key, "post:viewCount")) {
                ids.add(key.substring("rank:post:".length()));
            }
        }
        if (ids.isEmpty())
            return;
        // 需要更新的阅读量
        List<MPost> posts = postService.list(new QueryWrapper<MPost>().in("id", ids));
        // 遍历 并且从 redis 中获取出来
        posts.stream().forEach(post -> {
            Integer hget = (Integer) redisUtil.hget("rank:post:" + post.getId(), "post:viewCount");
            // 添加到 post 里
            post.setViewCount(hget);
        });
        if (posts.isEmpty())
            return;
        boolean inSucc = postService.updateBatchById(posts);
        if (inSucc) {
            ids.stream().forEach(id -> {
                redisUtil.hdel("rank:post:" + id, "post:viewCount");
            });
        }
    }
}

记得在主启动类里面添加
基于Springboot搭建个人博客 (学习笔记)_第88张图片

集成Shiro完成登录注册功能

注册功能的集成,先把我们的登录注册页面拿过来
基于Springboot搭建个人博客 (学习笔记)_第89张图片
login.ftl

<#include "../inc/layout.ftl"/>
<#-- 导入 layout 的标签 -->
<@layout "登录" >

<div class="layui-container fly-marginTop">
  <div class="fly-panel fly-panel-user" pad20>
    <div class="layui-tab layui-tab-brief" lay-filter="user">
      <ul class="layui-tab-title">
        <li class="/login">登入</li>
        <li><a href="/register">注册</a></li>
      </ul>
      <div class="layui-form layui-tab-content" id="LAY_ucm" style="padding: 20px 0;">
        <div class="layui-tab-item layui-show">
          <div class="layui-form layui-form-pane">
            <form method="post">
              <div class="layui-form-item">
                <label for="L_email" class="layui-form-label">邮箱</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_email" name="email" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <label for="L_pass" class="layui-form-label">密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_pass" name="pass" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <label for="L_vercode" class="layui-form-label">人类验证</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_vercode" name="vercode" required lay-verify="required" placeholder="请回答后面的问题" autocomplete="off" class="layui-input">
                </div>
                <div class="layui-form-mid">
                  <span style="color: #c00;">{{d.vercode}}</span>
                </div>
              </div>
              <div class="layui-form-item">
                <button class="layui-btn" lay-filter="*" lay-submit>立即登录</button>
                <span style="padding-left:20px;">
                  <a href="forget.html">忘记密码?</a>
                </span>
              </div>
              <div class="layui-form-item fly-form-app">
                <span>或者使用社交账号登入</span>
                <a href="" onclick="layer.msg('正在通过QQ登入', {icon:16, shade: 0.1, time:0})" class="iconfont icon-qq" title="QQ登入"></a>
                <a href="" onclick="layer.msg('正在通过微博登入', {icon:16, shade: 0.1, time:0})" class="iconfont icon-weibo" title="微博登入"></a>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>


<script src="../../res/layui/layui.js"></script>
<script>
layui.cache.page = 'user';
</script>
</@layout>

reg.ftl

<#include "../inc/layout.ftl"/>
<#-- 导入 layout 的标签 -->
<@layout "注册" >


<div class="layui-container fly-marginTop">
  <div class="fly-panel fly-panel-user" pad20>
    <div class="layui-tab layui-tab-brief" lay-filter="user">
      <ul class="layui-tab-title">
        <li><a href="/login">登入</a></li>
        <li class="/register">注册</li>
      </ul>
      <div class="layui-form layui-tab-content" id="LAY_ucm" style="padding: 20px 0;">
        <div class="layui-tab-item layui-show">
          <div class="layui-form layui-form-pane">
            <form method="post">
              <div class="layui-form-item">
                <label for="L_email" class="layui-form-label">邮箱</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_email" name="email"  autocomplete="off" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux">将会成为您唯一的登入名</div>
              </div>
              <div class="layui-form-item">
                <label for="L_username" class="layui-form-label">昵称</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_username" name="username"  autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <label for="L_pass" class="layui-form-label">密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_pass" name="password"  autocomplete="off" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux">616个字符</div>
              </div>
              <div class="layui-form-item">
                <label for="L_repass" class="layui-form-label">确认密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_repass" name="repass"  autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <label for="L_vercode" class="layui-form-label">人类验证</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_vercode" name="vercode"  placeholder="请填写后面的验证码" autocomplete="off" class="layui-input">
                </div>
                <div class="">
                  <img src="/captcha.jpg" id="captch"></img>
                </div>
              </div>
              <div class="layui-form-item">
                <button class="layui-btn" lay-filter="*" lay-submit alert="true">立即注册</button>
              </div>
              <div class="layui-form-item fly-form-app">
                <span>或者直接使用社交账号快捷注册</span>
                <a href="" onclick="layer.msg('正在通过QQ登入', {icon:16, shade: 0.1, time:0})" class="iconfont icon-qq" title="QQ登入"></a>
                <a href="" onclick="layer.msg('正在通过微博登入', {icon:16, shade: 0.1, time:0})" class="iconfont icon-weibo" title="微博登入"></a>
              </div>
            </form>
          </div>
        </div>
      </div>
    </div>
  </div>

</div>

<script src="../../res/layui/layui.js"></script>
<script>
layui.cache.page = 'user';
$("#captch").click(function () {
  this.src = "/captcha.jpg"
})
</script>

</@layout>

编写跳转的Controller可以进行页面的跳转

    @GetMapping("/login")
    public String login() {

        return "/auth/login";
    }

    @GetMapping("/register")
    public String register() {

        return "/auth/reg";
    }

现在可以进行简单的页面的跳转
编写注册功能
基于Springboot搭建个人博客 (学习笔记)_第90张图片

    //生成验证码
    @Autowired
    Producer producer;
    @GetMapping("/captcha.jpg")
    public void kaptcha(HttpServletResponse response) throws IOException {
        // 生成验证
        String text = producer.createText();
        // 生成图片
        BufferedImage image = producer.createImage(text);
        // 设置到session 会话属性
        request.getSession().setAttribute(KAPTCHA_SESSION, text);
        // 设置基本的信息
        response.setHeader("Cache-Control", "no-store,no-cache");
        response.setContentType("image/jpeg");
        ServletOutputStream outputStream = response.getOutputStream();
        ImageIO.write(image, "jpg", outputStream);

    }

前端获取这个Get路径就可以
基于Springboot搭建个人博客 (学习笔记)_第91张图片
验证码成功后开始编写注册的功能。这里有一个需要注意的是如何保证输入的验证码和生成的验证码一样,上面我们先存到了session,等集成shiro 后在修改暂时放到了session里面

@PostMapping("/register")
    @ResponseBody
    public Result doreg(MUser user, String repass, String vercode) {
        // 判断检验实体类
        ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(user);
        if (validResult.hasErrors()) {
            return Result.fail(validResult.getErrors());
        }
        //判断两次输入的密码是否正确
        if (!user.getPassword().equals(repass)) {
            return Result.fail("两次密码不正确");
        }
        // 获取到 用户的输入的验证码
        // 获取到session 会话属性
        String captcha = (String) request.getSession().getAttribute(KAPTCHA_SESSION);
        // 判断验证码是否
        if (vercode == null || !vercode.equalsIgnoreCase(captcha)) {
            return Result.fail("验证码不一致");
        }
        // 注册的方法
        Result result =userService.register(user);
        return result.success().action("/login");
    }

这里使用到了两个工具类,结果集和实体校验的工具类,都是网上找的代码就不复制的
服务类MUserServiceImpl


/**
 * 

* 服务实现类 *

* * @author fjj * @since 2021-07-23 */
@Service public class MUserServiceImpl extends ServiceImpl<MUserMapper, MUser> implements IMUserService { // 注入 Mapper @Autowired MUserMapper userMapper; @Override public Result register(MUser user) { // 判断是不是唯一的 int count = this.count(new QueryWrapper<MUser>().eq("email", user.getEmail()).or().eq("username", user.getUsername())); if (count > 0) Result.fail("用户名或者邮箱已经存在了,建议换个名字啦"); // 创建实体类 只写我们需要注册的字段 MUser temp = new MUser(); temp.setUsername(user.getUsername()); // 密码需要MD5 加密 temp.setPassword(SecureUtil.md5(user.getPassword())); temp.setEmail(user.getEmail()); temp.setCreated(new Date()); temp.setPoint(0); temp.setVipLevel(0); temp.setCommentCount(0); temp.setPostCount(0); temp.setAvatar("/res/images/avatar/default.png"); this.save(temp); return Result.success(); } }

注册流程结束

登录的流程
前面有登录的login的ftl就不粘贴过来了
Controller 类

    @PostMapping("/login")
    @ResponseBody
    public Result dologin(String email,String password,String vercode) {
        // 判断 用户名和密码 是不是为空
        if (StrUtil.isEmpty(email )|| StrUtil.isBlank(password)) {
            return Result.fail("不能为空");
        }
        // 获取到 token
        UsernamePasswordToken token = new UsernamePasswordToken(email, SecureUtil.md5(password));
        // 获取到 用户的输入的验证码
        // 获取到session 会话属性
        String captcha = (String) request.getSession().getAttribute(KAPTCHA_SESSION);
        // 判断验证码是否
        if (vercode == null || !vercode.equalsIgnoreCase(captcha)) {
            return Result.fail("验证码不一致");
        }
        try {
            SecurityUtils.getSubject().login(token);

        } catch (AuthenticationException e) {
            if (e instanceof UnknownAccountException) {
                return Result.fail("用户不存在");
            } else if (e instanceof LockedAccountException) {
                return Result.fail("用户被禁用");
            } else if (e instanceof IncorrectCredentialsException) {
                return Result.fail("密码错误");
            } else {
                return Result.fail("用户认证失败");
            }
        }

        return Result.success().action("/");
    }

这里用到了Shiro的所以要配置一下Shiro的配置文件securityManager

@Slf4j
@Configuration
public class ShiroConfig {


    @Bean
    public SecurityManager securityManager(AccountRealm accountRealm){

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(accountRealm);

        log.info("------------------>securityManager注入成功");

        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {

        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        // 配置登录的url和登录成功的url
        filterFactoryBean.setLoginUrl("/login");
        filterFactoryBean.setSuccessUrl("/user/center");
        // 配置未授权跳转页面
        filterFactoryBean.setUnauthorizedUrl("/error/403");

//        filterFactoryBean.setFilters(MapUtil.of("auth", authFilter()));

        Map<String, String> hashMap = new LinkedHashMap<>();

        hashMap.put("/res/**", "anon");

        hashMap.put("/user/home", "auth");
        hashMap.put("/user/set", "auth");
        hashMap.put("/user/upload", "auth");
        hashMap.put("/user/index", "auth");
        hashMap.put("/user/public", "auth");
        hashMap.put("/user/collection", "auth");
        hashMap.put("/user/mess", "auth");
        hashMap.put("/msg/remove/", "auth");
        hashMap.put("/message/nums/", "auth");

        hashMap.put("/collection/remove/", "auth");
        hashMap.put("/collection/find/", "auth");
        hashMap.put("/collection/add/", "auth");

        hashMap.put("/post/edit", "auth");
        hashMap.put("/post/submit", "auth");
        hashMap.put("/post/delete", "auth");
        hashMap.put("/post/reply/", "auth");

        hashMap.put("/websocket", "anon");
        hashMap.put("/login", "anon");
        return filterFactoryBean;

    }

重写AccountRealm

@Component
public class AccountRealm extends AuthorizingRealm {
    @Autowired
    IMUserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        // 调用登录的逻辑
        AccountProfile profile = userService.login(usernamePasswordToken.getUsername(), String.valueOf(usernamePasswordToken.getPassword()));
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName());
        return info;
    }
}

重写AccountProfile实体类用来反射字段使用的



@Data
public class AccountProfile implements Serializable {

    private Long id;

    private String username;
    private String email;
    private String sign;

    private String avatar;
    private String gender;
    private Date created;

    public String getSex() {
        return "0".equals(gender) ? "女" : "男";
    }

}

最后就是服务类了

 // 登录
    @Override
    public AccountProfile login(String email, String password) {
        MUser user1 = this.getOne(new QueryWrapper<MUser>().eq("email", email));
        if (user1 == null) {
            if (user1 == null) {
                throw new UnknownAccountException();
            }
            if (!user1.getPassword().equals(user1.getPassword())) {
                throw new IncorrectCredentialsException();
            }
            user1.setLasted(new Date());
            this.updateById(user1);
        }
        AccountProfile profile = new AccountProfile();
        BeanUtil.copyProperties(user1,profile);
        return profile;
    }

之上所以的登录基本流程 over了

修改登录之后导航栏显示自己信息以及退出登录

基于Springboot搭建个人博客 (学习笔记)_第92张图片
上面是登录前的,理想的状态应该是登录后显示每个人的基本信息
这里要用到一个SpringBoot 跟Shiro的一个标签
主要的标签博客
修改我们的header.ftl的页面
未登录用这个标签
基于Springboot搭建个人博客 (学习笔记)_第93张图片
登录之后的

基于Springboot搭建个人博客 (学习笔记)_第94张图片
效果
基于Springboot搭建个人博客 (学习笔记)_第95张图片
基于Springboot搭建个人博客 (学习笔记)_第96张图片
基于Springboot搭建个人博客 (学习笔记)_第97张图片
Controller

    // 退出登录页面
    @RequestMapping("/logout")
    public String logout() {
        // 清楚 session
        SecurityUtils.getSubject().logout();
        return "redirect:/";

    }

基本设置功能

个人主页问题

基于Springboot搭建个人博客 (学习笔记)_第98张图片

先把写Controller 类

@Controller
public class UserController extends BaseController {
    @GetMapping("/user/home")
    public String home() {
        // 获取到当前的 user
        MUser user = userService.getById(getProfilrId());
        // 获取到 该用户的基本信息
        List<MPost> posts = postService.list(new QueryWrapper<MPost>()
                .eq("user_id", getProfilrId())
                // 获取 30 天内的文章
                .gt("created", DateUtil.lastMonth())
                .orderByDesc("created")
        );
        request.setAttribute("user",user);
        request.setAttribute("posts",posts);

        // 设置到缓存
        return "/user/home";
    }
}

事先在BaseController 里面写入获取到Shiro id的方法

    // 获取到 shiro 里面的 用户 id
    public AccountProfile getProfile () {
        return (AccountProfile) SecurityUtils.getSubject().getPrincipal();
    }
    protected Long getProfilrId () {
        return getProfile().getId();
    }

前端页面

<#include "../inc/layout.ftl" />

<@layout "我的主页">

  <div class="fly-home fly-panel" style="background-image: url();">
    <img src="${user.avatar}" alt="${user.username}">
    <i class="iconfont icon-renzheng" title="Fly社区认证"></i>
    <h1>
      ${user.username}
      <i class="iconfont icon-nan"></i>
      <!-- <i class="iconfont icon-nv"></i>  -->
      <i class="layui-badge fly-badge-vip">VIP3</i>
      <!--
      <span style="color:#c00;">(管理员)</span>
      <span style="color:#5FB878;">(社区之光)</span>
      <span>(该号已被封)</span>
      -->
    </h1>



    <p class="fly-home-info">
<#--      <i class="iconfont icon-kiss" title="飞吻"></i><span style="color: #FF7200;">66666 飞吻</span>-->
      <i class="iconfont icon-shijian"></i><span> ${timeAgo(user.created)}加入</span>
      <i class="iconfont icon-chengshi"></i><span>${user.address}</span>
    </p>

    <p class="fly-home-sign">(${user.sign!'这个人好懒,什么都没留下!'}</p>

<#--    <div class="fly-sns" data-user="">-->
<#--      <a href="javascript:;" class="layui-btn layui-btn-primary fly-imActive" data-type="addFriend">加为好友</a>-->
<#--      <a href="javascript:;" class="layui-btn layui-btn-normal fly-imActive" data-type="chat">发起会话</a>-->
<#--    </div>-->

  </div>

  <div class="layui-container">
    <div class="layui-row layui-col-space15">
      <div class="layui-col-md6 fly-home-jie">
        <div class="fly-panel">
          <h3 class="fly-panel-title">${user.username} 最近的提问</h3>
          <ul class="jie-row">
            <#list posts as post>
              <li>
                <#if post.recommend><span class="fly-jing"></span></#if>
                <a href="/post/${post.id}" class="jie-title"> ${post.title}</a>
                <i>${timeAgo(post.created)}</i>
                <em class="layui-hide-xs">${post.viewCount}/${post.commentCount}</em>
              </li>
            </#list>
            <#if posts??>
              <div class="fly-none" style="min-height: 50px; padding:30px 0; height:auto;">
                <i style="font-size:14px;">没有发表任何求解</i>
              </div>
            </#if>
          </ul>
        </div>
      </div>

      <div class="layui-col-md6 fly-home-da">
        <div class="fly-panel">
          <h3 class="fly-panel-title">${user.username} 最近的回答</h3>
          <ul class="home-jieda">
            <div class="fly-none" style="min-height: 50px; padding:30px 0; height:auto;"><span>没有回答任何问题</span></div>
          </ul>
        </div>
      </div>
    </div>
  </div>

</@layout>

效果
基于Springboot搭建个人博客 (学习笔记)_第99张图片

基本设置功能

先修改我们的页面,把Set.ftl 页面复制过来

<#include "../inc/layout.ftl" />

<@layout "基本设置">

  <div class="layui-container fly-marginTop fly-user-main">
    <ul class="layui-nav layui-nav-tree layui-inline" lay-filter="user">
      <li class="layui-nav-item">
        <a href="/user/home">
          <i class="layui-icon">&#xe609;</i>
          我的主页
        </a>
      </li>
<#--      <li class="layui-nav-item">-->
<#--        <a href="index.html">-->
<#--          <i class="layui-icon">&#xe612;</i>-->
<#--          用户中心-->
<#--        </a>-->
<#--      </li>-->
      <li class="layui-nav-item layui-this">
        <a href="/user/set">
          <i class="layui-icon">&#xe620;</i>
          基本设置
        </a>
      </li>
      <li class="layui-nav-item">
        <a href="/user/mess">
          <i class="layui-icon">&#xe611;</i>
          我的消息
        </a>
      </li>
    </ul>

    <div class="site-tree-mobile layui-hide">
      <i class="layui-icon">&#xe602;</i>
    </div>
    <div class="site-mobile-shade"></div>

    <div class="site-tree-mobile layui-hide">
      <i class="layui-icon">&#xe602;</i>
    </div>
    <div class="site-mobile-shade"></div>


    <div class="fly-panel fly-panel-user" pad20>
      <div class="layui-tab layui-tab-brief" lay-filter="user">
        <ul class="layui-tab-title" id="LAY_mine">
          <li class="layui-this" lay-id="info">我的资料</li>
          <li lay-id="avatar">头像</li>
          <li lay-id="pass">密码</li>
        </ul>
        <div class="layui-tab-content" style="padding: 20px 0;">
          <div class="layui-form layui-form-pane layui-tab-item layui-show">

            <form method="post">
              <div class="layui-form-item">
                <label for="L_email" class="layui-form-label">邮箱</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_email" name="email" required lay-verify="email" autocomplete="off" value="${user.email}" class="layui-input" readonly>
                </div>
                <div class="layui-form-mid layui-word-aux">如果您在邮箱已激活的情况下,变更了邮箱,需<a href="activate.html" style="font-size: 12px; color: #4f99cf;">重新验证邮箱</a></div>
              </div>
              <div class="layui-form-item">
                <label for="L_username" class="layui-form-label">昵称</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_username" name="username" required lay-verify="required" value="${user.username}" autocomplete="off" value="" class="layui-input">
                </div>
                <div class="layui-inline">
                  <div class="layui-input-inline">
                    <input type="radio" name="sex" value="0" <#if user.gender =='0'>checked</#if> title="男">
                    <input type="radio" name="sex" value="1" <#if user.gender =='1'>checked</#if> title="女">
                  </div>
                </div>

              </div>
              <div class="layui-form-item">
                <label for="L_city" class="layui-form-label">城市</label>
                <div class="layui-input-inline">
                  <input type="text" id="L_city" name="address" autocomplete="off" value="${user.address}" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item layui-form-text">
                <label for="L_sign" class="layui-form-label">签名</label>
                <div class="layui-input-block">
                  <textarea placeholder="随便写些什么刷下存在感" id="L_sign"  name="sign" autocomplete="off" class="layui-textarea" style="height: 80px;">${user.sign}</textarea>
                </div>
              </div>
              <div class="layui-form-item">
                <button class="layui-btn" key="set-mine" lay-filter="*" lay-submit alert="true" reload="true">确认修改</button>
              </div>
            </form>
        </div>
          <div class="layui-form layui-form-pane layui-tab-item">
            <div class="layui-form-item">
              <div class="avatar-add">
                <p>建议尺寸168*168,支持jpg、png、gif,最大不能超过50KB</p>
                <button type="button" class="layui-btn upload-img">
                  <i class="layui-icon">&#xe67c;</i>上传头像
                </button>
                <img src="https://tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg">
                <span class="loading"></span>
              </div>
            </div>
          </div>

          <div class="layui-form layui-form-pane layui-tab-item">
            <form action="/user/repass" method="post">
              <div class="layui-form-item">
                <label for="L_nowpass" class="layui-form-label">当前密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_nowpass" name="nowpass" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <label for="L_pass" class="layui-form-label">新密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_pass" name="pass" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux">616个字符</div>
              </div>
              <div class="layui-form-item">
                <label for="L_repass" class="layui-form-label">确认密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_repass" name="repass" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <button class="layui-btn" key="set-mine" lay-filter="*" lay-submit>确认修改</button>
              </div>
            </form>
          </div>

        </div>

    </div>




    </div>
  </div>
  <script>
    layui.cache.page = 'user';
  </script>
</@layout>

修改后端的Controller类

 // 基本设置
    @GetMapping ("/user/set")
    public String set () {
        // 获取到当前的 user
        MUser user1 = userService.getById(getProfilrId());
        request.setAttribute("user", user1);
        return "/user/set";
    }
    @PostMapping("/user/set")
    @ResponseBody
    public Result set(MUser user) {

        //判断名字是否为空
        if (StrUtil.isBlank(user.getUsername())) {
            return Result.fail("昵称为空了");
        }
        // 判断 是否名字被占用了
        int count = userService.count(new QueryWrapper<MUser>()
                .eq("username", getProfile().getUsername())
                .ne("id", getProfilrId()));
        if (count >0) {
            return Result.fail("名字已经存在");

        }
        MUser temp = userService.getById(getProfilrId());
        temp.setUsername(user.getUsername());
        temp.setGender(user.getGender());
        temp.setSign(user.getSign());
        temp.setAddress(user.getAddress());
        userService.updateById(temp);
        AccountProfile profile = getProfile();
        profile.setAddress(temp.getAddress());
        profile.setUsername(temp.getUsername());
        profile.setAvatar(temp.getAvatar());
        profile.setGender(temp.getGender());
        profile.setSign(temp.getSign());

        return Result.success().action("/user/set#info");
    }

效果
基于Springboot搭建个人博客 (学习笔记)_第100张图片

上传头像的功能

前端页面

          <div class="layui-form layui-form-pane layui-tab-item">
            <div class="layui-form-item">
              <div class="avatar-add">
                <p>建议尺寸168*168,支持jpg、png、gif,最大不能超过50KB</p>
                <button type="button" class="layui-btn upload-img">
                  <i class="layui-icon">&#xe67c;</i>上传头像
                </button>
                <img src="${user.avatar}">
                <span class="loading"></span>
              </div>
            </div>
          </div>

需要用到上传工具类

@Slf4j
@Component
public class UploadUtil {

    @Autowired
    Consts consts;

    public final static String type_avatar = "avatar";

    public Result upload(String type, MultipartFile file) throws IOException {

        if(StrUtil.isBlank(type) || file.isEmpty()) {
            return Result.fail("上传失败");
        }

        // 获取文件名
        String fileName = file.getOriginalFilename();
        log.info("上传的文件名为:" + fileName);
        // 获取文件的后缀名
        String suffixName = fileName.substring(fileName.lastIndexOf("."));
        log.info("上传的后缀名为:" + suffixName);
        // 文件上传后的路径
        String filePath = consts.getUploadDir();

        if ("avatar".equalsIgnoreCase(type)) {
            AccountProfile profile = (AccountProfile) SecurityUtils.getSubject().getPrincipal();
            fileName = "/avatar/avatar_" + profile.getId() + suffixName;

        } else if ("post".equalsIgnoreCase(type)) {
            fileName = "/post/post_" + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + suffixName;
        }

        File dest = new File(filePath + fileName);
        // 检测是否存在目录
        if (!dest.getParentFile().exists()) {
            dest.getParentFile().mkdirs();
        }
        try {
            file.transferTo(dest);
            log.info("上传成功后的文件路径未:" + filePath + fileName);

            String path = filePath + fileName;
            String url = "/upload" + fileName;

            log.info("url ---> {}", url);

            return Result.success(url);
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return Result.success(null);

    }

}

控制层

 @PostMapping("/user/set")
    @ResponseBody
    public Result set(MUser user) {
        // 这里写上传头像的路径 由于layui 的js 封装的 /user/set
        if (StrUtil.isNotBlank(user.getAvatar())) {
            // 获取到当前用户
            MUser user1 = userService.getById(getProfilrId());
            user1.setAvatar(user.getAvatar());
            userService.updateById(user1);
            AccountProfile profile = getProfile();
            profile.setAvatar(user.getAvatar());
            return Result.success().action("/user/set#avatar");
        }

        //判断名字是否为空
        if (StrUtil.isBlank(user.getUsername())) {
            return Result.fail("昵称为空了");
        }
        // 判断 是否名字被占用了
        int count = userService.count(new QueryWrapper<MUser>()
                .eq("username", getProfile().getUsername())
                .ne("id", getProfilrId()));
        if (count > 0) {
            return Result.fail("名字已经存在");

        }
        MUser temp = userService.getById(getProfilrId());
        temp.setUsername(user.getUsername());
        temp.setGender(user.getGender());
        temp.setSign(user.getSign());
        temp.setAddress(user.getAddress());
        userService.updateById(temp);
        AccountProfile profile = getProfile();
        profile.setAddress(temp.getAddress());
        profile.setUsername(temp.getUsername());
        profile.setAvatar(temp.getAvatar());
        profile.setGender(temp.getGender());
        profile.setSign(temp.getSign());

        return Result.success().action("/user/set#info");
    }

    // 上传头像
    @PostMapping("/user/upload")
    @ResponseBody
    public Result upload(@RequestParam(value = "file") MultipartFile file) throws Exception {
        return uploadUtil.upload(UploadUtil.type_avatar, file);
    }

js 是layui 封装好的
基于Springboot搭建个人博客 (学习笔记)_第101张图片
效果
基于Springboot搭建个人博客 (学习笔记)_第102张图片

修改密码的功能

前端页面

            <form action="/user/repass" method="post">
              <div class="layui-form-item">
                <label for="L_nowpass" class="layui-form-label">当前密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_nowpass" name="nowpass" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <label for="L_pass" class="layui-form-label">新密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_pass" name="pass" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
                <div class="layui-form-mid layui-word-aux">616个字符</div>
              </div>
              <div class="layui-form-item">
                <label for="L_repass" class="layui-form-label">确认密码</label>
                <div class="layui-input-inline">
                  <input type="password" id="L_repass" name="repass" required lay-verify="required" autocomplete="off" class="layui-input">
                </div>
              </div>
              <div class="layui-form-item">
                <button class="layui-btn" key="set-mine" lay-filter="*" lay-submit alert="true" reload="true">确认修改</button>
              </div>
            </form>

控制层

   // 修改 密码

    @ResponseBody
    @PostMapping("/user/repass")
    public Result repass(String nowpass, String pass, String repass) {
        if(!pass.equals(repass)) {
            return Result.fail("两次密码不相同");
        }

        MUser user = userService.getById(getProfilrId());

        String nowPassMd5 = SecureUtil.md5(nowpass);
        if(!nowPassMd5.equals(user.getPassword())) {
            return Result.fail("密码不正确");
        }

        user.setPassword(SecureUtil.md5(pass));
        userService.updateById(user);

        return Result.success().action("/user/set#pass");

    }

用户中心发表的贴和收藏的贴

这里的分页用流的形式做,参考一下layui官网
基于Springboot搭建个人博客 (学习笔记)_第103张图片

前端的页面

<#include "../inc/layout.ftl" />

<@layout "用户中心">
  <div class="layui-container fly-marginTop fly-user-main">
    <@centerLeft level=1></@centerLeft>


    <div class="site-tree-mobile layui-hide">
      <i class="layui-icon">&#xe602;</i>
    </div>
    <div class="site-mobile-shade"></div>

    <div class="site-tree-mobile layui-hide">
      <i class="layui-icon">&#xe602;</i>
    </div>
    <div class="site-mobile-shade"></div>


    <div class="fly-panel fly-panel-user" pad20>
      <!--
      <div class="fly-msg" style="margin-top: 15px;">
        您的邮箱尚未验证,这比较影响您的帐号安全,<a href="activate.html">立即去激活?</a>
      </div>
      -->
      <div class="layui-tab layui-tab-brief" lay-filter="user">
        <ul class="layui-tab-title" id="LAY_mine">
          <li data-type="mine-jie" lay-id="index" class="layui-this">我发的帖(<span>89</span></li>
          <li data-type="collection" data-url="/collection/find/" lay-id="collection">我收藏的帖(<span>16</span></li>
        </ul>
        <div class="layui-tab-content" style="padding: 20px 0;">
          <div class="layui-tab-item layui-show">
            <ul class="mine-view jie-row" id="fabu">
              <script id="tpl-fabu" type="text/html">
                <li>
                  <a class="jie-title" href="/detail/{{d.id}}" target="_blank">{{ d.title }}</a>
                  <i>{{layui.util.toDateString(d.created, 'yyyy-MM-dd HH:mm:ss')}}</i>
                  <a class="mine-edit" href="/post/edit?id={{d.id}}">编辑</a>
                  <em>{{d.viewCount}}/{{d.commentCount}}</em>
                </li>
              </script>
            </ul>
            <div id="LAY_page"></div>
          </div>
          <div class="layui-tab-item">
            <ul class="mine-view jie-row" id="collection">
              <script id="tpl-collection" type="text/html">
                <li>
                  <a class="jie-title" href="/detail/{{d.id}}" target="_blank">{{d.title}}</a>
                  <i>收藏于{{layui.util.timeAgo(d.created, true)}}</i>
                </li>
              </script>
            </ul>
            <div id="LAY_page1"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script>
    layui.cache.page = 'user';
    layui.use(['laytpl', 'flow', 'util'], function() {
      var $ = layui.jquery;
      var laytpl = layui.laytpl;
      var flow = layui.flow;
      var util = layui.util;

      flow.load({
        elem: '#fabu' //指定列表容器
        ,isAuto: false
        ,done: function(page, next){
          var lis = [];

          $.get('/user/public?start='+page, function(res){
            layui.each(res.data.records, function(index, item){

              var tpl = $("#tpl-fabu").html();
              laytpl(tpl).render(item, function (html) {
                $("#fabu .layui-flow-more").before(html);
              });
            });

            next(lis.join(''), page < res.data.pages);
          });
        }
      });

      flow.load({
        elem: '#collection'
        ,isAuto: false
        ,done: function(page, next){
          var lis = [];

          $.get('/user/collection?pn='+page, function(res){
            layui.each(res.data.records, function(index, item){

              var tpl = $("#tpl-collection").html();
              laytpl(tpl).render(item, function (html) {
                $("#collection .layui-flow-more").before(html);
              });
            });

            next(lis.join(''), page < res.data.pages);
          });
        }
      });
    });
  </script>
</@layout>


后端的Controller

 // 发表的文章
    @GetMapping("/user/public")
    @ResponseBody
    public Result PublicFaBu() {
        // 获取到分页信息
        IPage page = postService.page(getPage(), new QueryWrapper<MPost>()
                .eq("user_id", getProfilrId())
                .orderByDesc("created"));

        return Result.success(page);
    }

    // 收藏的帖子
    @GetMapping("/user/collection")
    @ResponseBody
    public Result Collection() {
        IPage page = postService.page(getPage(), new QueryWrapper<MPost>()
                .inSql("id", "select post_id from m_user_collection where user_id = " + getProfilrId())
        );
        return Result.success(page);
    }

效果
基于Springboot搭建个人博客 (学习笔记)_第104张图片

消息中心

我的消息先导入前端的页面

<#include "../inc/layout.ftl" />

<@layout "用户中心">
  <div class="layui-container fly-marginTop fly-user-main">
    <@centerLeft level=3></@centerLeft>

    <div class="site-tree-mobile layui-hide">
      <i class="layui-icon">&#xe602;</i>
    </div>
    <div class="site-mobile-shade"></div>

    <div class="site-tree-mobile layui-hide">
      <i class="layui-icon">&#xe602;</i>
    </div>
    <div class="site-mobile-shade"></div>


    <div class="fly-panel fly-panel-user" pad20>
      <div class="layui-tab layui-tab-brief" lay-filter="user" id="LAY_msg" style="margin-top: 15px;">
        <button class="layui-btn layui-btn-danger" id="LAY_delallmsg">清空全部消息</button>
        <div  id="LAY_minemsg" style="margin-top: 10px;">
          <!--<div class="fly-none">您暂时没有最新消息</div>-->
          <ul class="mine-msg">
            <#list pageData.records as mess>

              <li data-id="${mess.id}">
                <blockquote class="layui-elem-quote">
                  <#if mess.type == 0>
                    系统消息:${mess.content}
                  </#if>
                  <#if mess.type == 1>
                    ${mess.fromUserName} 评论了你的文章 <${mess.postTitle}>,内容是 (${mess.content})
                  </#if>
                  <#if mess.type == 2>
                    ${mess.fromUserName} 回复了你的评论 (${mess.content}),文章是 <${mess.postTitle}>
                  </#if>

                </blockquote>
                <p><span>${timeAgo(mess.creted)}</span><a href="javascript:;" class="layui-btn layui-btn-small layui-btn-danger fly-delete">删除</a></p>
              </li>
            </#list>
          </ul>
          <@paging pageData></@paging>
        </div>
      </div>
    </div>

  </div>
  <script>
    layui.cache.page = 'user';
  </script>
</@layout>

开始写Controller这里我们用的自己的方法,所以要自己写sql


    // 基本设置消息
    @GetMapping("/user/mess")
    public String Mess1() {
        IPage page = userMessageService.pageing(getPage(), new QueryWrapper<MUserMessage>()
                .eq("to_user_id", getProfilrId())
                .orderByAsc("created")
        );
        request.setAttribute("pageData", page);

        return "/user/mess";
    }

pageing方法



/**
 * 

* 服务实现类 *

* * @author fjj * @since 2021-07-23 */
@Service public class MUserMessageServiceImpl extends ServiceImpl<MUserMessageMapper, MUserMessage> implements IMUserMessageService { @Autowired MUserMessageMapper mUserMessageMapper; @Override public IPage pageing(Page page, QueryWrapper<MUserMessage> wrapper) { return mUserMessageMapper.selectMessages(page,wrapper); } }

Mapperxml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.springbootblog.mapper.MUserMessageMapper">
<select id="selectMessages" resultType="com.example.springbootblog.vo.UserMessageVo">
    SELECT
        m.*, (
        SELECT
            username
        FROM
            `m_user`
        WHERE
            id = m.from_user_id
    ) AS fromUserName,
        (
            SELECT
                title
            FROM
                `m_post`
            WHERE
                id = m.post_id
        ) AS postTitle
    FROM
        `m_user_message` m

        ${ew.customSqlSegment}
select>
mapper>

一定要记得在Mapper 里面写上一个注解

/**
 * 

* Mapper 接口 *

* * @author fjj * @since 2021-07-23 */
@Mapper public interface MUserMessageMapper extends BaseMapper<MUserMessage> { IPage<UserMessageVo> selectMessages(Page page, @Param(Constants.WRAPPER) QueryWrapper<MUserMessage> wrapper); }

因为可能原来的实体类没有办法帮我们映射完所以新写了一个VO


@Data
public class UserMessageVo extends MUserMessage {
    private String toUserName;
    private String fromUserName;
    private String postTitle;
    private String commentContent;
}

效果
基于Springboot搭建个人博客 (学习笔记)_第105张图片
删除消息功能Controller

    // 删除消息功能
    @PostMapping("/msg/remove/")
    @ResponseBody
    public Result msgRemove(Long id, @RequestParam(defaultValue = "false") boolean all) {

        boolean remove = userMessageService.remove(new QueryWrapper<MUserMessage>()
                .eq("to_user_id", getProfilrId())
                .eq(!all, "id", id));
        return remove ? Result.success(null) : Result.fail("删除失败了哦!");
    }

基于Springboot搭建个人博客 (学习笔记)_第106张图片

下面的内容地址

续篇

你可能感兴趣的:(基于Springboot搭建个人博客 (学习笔记))