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

技术选型

主要框架

核心框架 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
在这里插入图片描述

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

erlang环境安装程序下载路径: https://www.erlang.org/downloads
在这里插入图片描述

安装配置 erlang

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

接下来配置环境变量,常规操作,新建系统变量-键入变量名ERLANG_HOME,键入变量值:erlang安装路径。如下图:
在这里插入图片描述
然后添加系统path路径中,添加 : %ERLANG_HOME%\bin

在这里插入图片描述
然后打开cmd,输入erl,看到我们的erlang版本号,就说明安装成功了

在这里插入图片描述

安装配置 rabbitMQ

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

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

rabbitmq-plugins enable rabbitmq_management

在这里插入图片描述

验证

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

在这里插入图片描述
这就说明我们安装成功了。

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

安装Elasticsearch

下载地址

https://www.elastic.co/cn/elasticsearch/ 官网
在这里插入图片描述
在这里插入图片描述
下载完成后
在这里插入图片描述
目录结构
bin :启动文件
config 配置文件
log4j2 :日志文件
jvm.options java 虚拟机相关的配置
elasticsearch.yml elasticsearch 的配置文件
lib 相关jar 包
logs 日志
modules 功能模块
plugins 插件 ik

在bin 里点击启动即可
在这里插入图片描述
在这里插入图片描述
安装head 插件
在 github 下载 https://github.com/mobz/elasticsearch-head/
简单的前端页面
安装 cnpm install
在这里插入图片描述
在这里插入图片描述
**此时还不能链接上我们的 es **
在这里插入图片描述
连接不上解决一下跨域的问题
在ElasticSearch.yml 加上这两句话
http.cors.enabled: true
http.cors.allow-origin: “*”

此时就可以连接上来了
在这里插入图片描述

创建Springboot 项目

这个操作比较简单

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>Springboot-blog</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Springboot-blog</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId><!--netty-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--mp-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!-- sql分析器 -->
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.8.6</version>
        </dependency>

        <!-- commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.1.17</version>
        </dependency>

        <!--验证码-->
        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>0.0.9</version>
        </dependency>

        <!--shiro权限框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

        <dependency>
            <groupId>net.mingsoft</groupId>
            <artifactId>shiro-freemarker-tags</artifactId>
            <version>0.1</version>
        </dependency>

        <!--websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <!-- es 6.4.3版本 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId><!--netty-->
            <version>2.1.1.RELEASE</version>
        </dependency>

        <!--整合rabbitmq-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>1.1.0</version>
        </dependency>

        <!-- tio -->
        <dependency>
            <groupId>org.t-io</groupId>
            <artifactId>tio-websocket-server</artifactId>
            <version>3.2.5.v20190101-RELEASE</version>
        </dependency>

    </dependencies>

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

</project>

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

我放在了 资源里面 可以下载到https://download.csdn.net/download/m0_46937429/20398937?spm=1001.2014.3001.5503
在这里插入图片描述
大概框架是这个样子.下载在我的资源里面

拆分首页 以及填充

先把 下载好的 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";
    }
}

在这里插入图片描述

在这里插入图片描述

此时访问 http://localhost:8080/
在这里插入图片描述
我们可以把 头信息抽离出来 再用 freemarker 引入就可以了
在这里插入图片描述
在这里插入图片描述
然后在 index.ftl 里面引入就可以了
在这里插入图片描述
同样的道理把 导航的部分跟 尾部抽离出来就可以了
中间部分处理
在这里插入图片描述

可以先把右边的部分抽离出来
在这里插入图片描述
这个 right.ftl 里面就是 那个 md 4 的 同样的道理 把 左侧的可以 抽离出来

这个时候还不太够完善,有好多的地方可以在所有的地方会用到
**新建一个 layout.ftl **
在这里插入图片描述

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

<#macro layout title>

</macro>

在这里插入图片描述

怎么引用我们写的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>

运行访问 ,其实就是拼图 套娃。哈哈哈哈
在这里插入图片描述
还可以访问,证明我们的抽离没有问题
添加 页面
在这里插入图片描述
在这里插入图片描述
把这两个页面复制过来

先写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";
    }
}

效果
在这里插入图片描述
在这里插入图片描述
是没有问题的。但是不能用 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的依赖包

   <!--mp-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.2.0</version>
        </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
在这里插入图片描述

这样启动的时候就不会报错了
在这里插入图片描述
说白了就是现在上面的那些导航栏是死的,我们应该从数据库里面去查询我们的数据。变成项目启动的时候动态出来我们的数据就可以了 。

创建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;
    }
}

修改
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
变为动态的了,但是还是有一个小的bug 就是当我们点击的时候首页那个绿色不会消失
创建一个公共的Controller 类,主要放一些公共需要继承的东西在这里插入图片描述

修改我们的PostController 类 设置属性
在这里插入图片描述
在这里插入图片描述
效果这个时候我们点击分享的时候首页就不会是绿色的,只有分享是绿色的
在这里插入图片描述

处理分页的问题

引入Mp 的分页插件
在这里插入图片描述
在这里插入图片描述
编写首页的Controller
解释为啥用这个方法

在这里插入图片描述
源码
在这里插入图片描述
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 实现类

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @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

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @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个表连起来

<?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.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

重启项目
在这里插入图片描述
这个SQL是因为在我们的Config 里面有个
在这里插入图片描述
前端页面获取
在这里插入图片描述
这里之所以是 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
在这里插入图片描述

这样的话就页面就可以访问到我们数据库的数据了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
效果
在这里插入图片描述
虽然渲染出来了,但是不是我们想要的数据。我们应该是存我们的后台返回出来的数据进行分页
在这里插入图片描述
修改前端的地方
在这里插入图片描述

在这里插入图片描述
layui 官网对于分页的这几个对象属性的解释
在这里插入图片描述
虽然有了从数据库出来的分页的数据的效果但是当我们点击第二页的时候数据并不会变化,所以还需要在修改一下代码layui 官网上就有
在这里插入图片描述

复制过来
在这里插入图片描述
在这里插入图片描述
抽取出来分页,因为分页不可能只在这一个地方出现
在这里插入图片描述

修改index.ftl 页面
在这里插入图片描述
不要忘记在layout里面添加标签
在这里插入图片描述
至此分页功能结束

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

两种方法

  1. 用JS 进行渲染
  2. 用freemarker标签
    在这里插入图片描述

编写freemarker的接口
在这里插入图片描述
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继承刚的方法
在这里插入图片描述

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());
    }

}

修改前端就可以了
在这里插入图片描述
效果
在这里插入图片描述

错误记录

刚开始的时候我的日期一直是未知 ,打断点发现前台传给后台的时候一直就是null 所以一直返回的未知
在这里插入图片描述
打断点调试
在这里插入图片描述
最后在实体类的发现用的日期格式化是
在这里插入图片描述
换成Date 之后
发现可以使用了
总结可能是因为我们继承的freemarker 的接口他们规定的 yyyy-MM-dd 的格式

修改置顶部分

创建自定义的标签
在这里插入图片描述
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);
    }


}

修改前端这里的页面
在这里插入图片描述
在这里插入图片描述
把原来的这部分代码抽离出来添加到common.ftl。方便调用
在这里插入图片描述
抽离出来之后
在这里插入图片描述
index.ftl 页面
在这里插入图片描述
效果
在这里插入图片描述数据库中置顶的也只有一篇
在这里插入图片描述

详情页面修改

点击标题到详情页面功能

在这里插入图片描述

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>

效果
在这里插入图片描述

修改提问的页面

在这里插入图片描述
修改前端的页面我们可以用我们自己自定义的标签
在这里插入图片描述

分页的信息要在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";
    }

效果
在这里插入图片描述

处理评论问题

在这里插入图片描述
处理这里的评论
编写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";
    }

评论的服务类

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @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>

效果
在这里插入图片描述

本周热议功能

在这里插入图片描述

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

文章的浏览量功能

当我们刷新页面浏览量应该加1,但是如果一直刷新一直差数据库的话会对数据库造成压力,所以可以先夹缓存里面然后定时添加到数据库
先写入缓存
在这里插入图片描述

    @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());

    }

现在可以存到缓存里面了,设置定时的功能
在这里插入图片描述

@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");
            });
        }
    }
}

记得在主启动类里面添加
在这里插入图片描述

集成Shiro完成登录注册功能

注册功能的集成,先把我们的登录注册页面拿过来
在这里插入图片描述
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";
    }

现在可以进行简单的页面的跳转
编写注册功能
在这里插入图片描述

    //生成验证码
    @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路径就可以
在这里插入图片描述
验证码成功后开始编写注册的功能。这里有一个需要注意的是如何保证输入的验证码和生成的验证码一样,上面我们先存到了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


/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @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 跟Shiro的一个标签
主要的标签博客
修改我们的header.ftl的页面
未登录用这个标签
在这里插入图片描述
登录之后的

在这里插入图片描述
效果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Controller

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

    }

基本设置功能

个人主页问题

在这里插入图片描述

先把写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>

效果
在这里插入图片描述

基本设置功能

先修改我们的页面,把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");
    }

效果
在这里插入图片描述

上传头像的功能

前端页面

          <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 封装好的
在这里插入图片描述
效果
在这里插入图片描述

修改密码的功能

前端页面

            <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官网
在这里插入图片描述

前端的页面

<#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);
    }

效果
在这里插入图片描述

消息中心

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

<#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方法



/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @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

<?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.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 里面写上一个注解

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @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;
}

效果
在这里插入图片描述
删除消息功能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("删除失败了哦!");
    }

在这里插入图片描述

下面的内容地址

续篇

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
好的,我可以和你分享一些关于Spring Boot的学习笔记。 1. Spring Boot是什么? Spring Boot是一个基于Spring框架的快速开发框架,它能够帮助开发者快速搭建Spring项目,简化了Spring应用开发的繁琐过程,提高了开发效率。 2. Spring Boot的优点有哪些? Spring Boot的优点有很多,其中包括: - 简化了Spring应用的开发,提高了开发效率; - 集成了很多常用的第三方库,减少了依赖管理的工作; - 自动化配置,减少了配置文件的编写工作; - 内嵌了Tomcat等Web容器,使得应用的部署更加便捷; - 提供了Actuator等模块,使得应用的监控和管理更加便捷。 3. Spring Boot的核心注解有哪些? Spring Boot的核心注解包括: - @SpringBootApplication:标注在启动类上,代表这是一个Spring Boot应用; - @Controller:标注在控制器类上,处理HTTP请求; - @Service:标注在服务类上,用于处理业务逻辑; - @Repository:标注在数据访问类上,用于数据库访问; - @Configuration:标注在配置类上,用于配置Spring应用上下文。 4. Spring Boot的配置文件有哪些? Spring Boot的配置文件包括: - application.properties:基于Key-Value的属性文件; - application.yml:基于YAML语法的配置文件。 5. 如何使用Spring Boot集成数据库? 使用Spring Boot集成数据库需要完成以下几个步骤: - 在pom.xml中添加相关数据库依赖; - 配置数据源和JPA/Hibernate等相关配置; - 编写实体类和DAO层代码。 以上就是一些关于Spring Boot的学习笔记,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值