SSM项目实战【从 0 到 1】:个人博客

文章目录

前言

本次项目,完全从 0 到 1,从创建项目开始手把手教程,源码地址:个人博客

一、项目简介

基本功能:

  1. 框架升级:SSM
  2. 密码升级:明文存储/md5存储 -> 加盐处理
  3. 用户登录状态持久化升级:session内存 -> 持久化到Redis/MySQL
  4. 功能升级:分页功能
  5. 使用拦截器升级用户登录验证

二、项目技术栈

前端:HTML + CSS + JS + Jquery
后端: Spring MVC + Spring Boot + Mybatis + Redis
数据库: MySQL
开发环境:Windows 10 、IDEA
项目构建工具: maven

三、准备工作

1、Spring Boot 项目创建

在这里插入图片描述
在这里插入图片描述
创建完成是这样的:
在这里插入图片描述

2、mybatis 配置

在 resource 下面创建 application.yml
在这里插入图片描述
在 application.yml 中添加 mybatis 配置:

# 配置数据库的连接字符串
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8
    username: root
    password: 12345678
    driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
  level:
    com:
      example:
        demo: debug

在 resource 下面创建 mapper 包:
在这里插入图片描述

3、数据库创建

SQL:

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
use mycnblog;

-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null UNIQUE,
    password varchar(64) not null,
    photo varchar(500) default '',
    createtime datetime default now(),
    updatetime datetime default now(),
    `state` int default 1
) default charset 'utf8mb4';

-- 创建文章表
drop table if exists  articleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime datetime default now(),
    updatetime datetime default now(),
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';

-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES 
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

-- 文章添加测试数据
insert into articleinfo(title,content,uid)
    values('Java','Java正文',1);

四、基本框架搭建

1、实体层(model)

实体层就直接实现了,就两个表对应的实体类

在这里插入图片描述
UserInfo:

package com.example.mycnblog.model;

import lombok.Data;

@Data
public class UserInfo {
    
    
    /**
     * 用户id
     */
    private Integer id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 电话号码
     */
    private String photo;
    /**
     * 创建时间
     */
    private String createTime;
    /**
     * 修改时间
     */
    private String updateTime;
    /**
     * 状态
     */
    private Integer state;
}

ArticleInfo:

package com.example.mycnblog.model;

import lombok.Data;

@Data
public class ArticleInfo {
    
    
    /**
     * 文章id
     */
    private Integer id;
    /**
     * 标题
     */
    private String title;
    /**
     * 正文
     */
    private String content;
    /**
     * 创建时间
     */
    private String createTime;
    /**
     * 修改时间
     */
    private String updateTime;
    /**
     * 发布文章的uid
     */
    private Integer uid;
    /**
     * 访问量
     */
    private Integer rcount;
    /**
     * 状态
     */
    private Integer state;
}

2、控制器层(controller)

控制层,具体内容等功能实现的时候再来完善

在这里插入图片描述
UserController:

package com.example.mycnblog.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

}

ArticleController:

package com.example.mycnblog.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

}

3、服务层(service)

服务层,具体内容之后完善

在这里插入图片描述
UserService:

package com.example.mycnblog.service;

import org.springframework.stereotype.Service;

/**
 * 用户表的服务层
 */
@Service
public class UserService {
    
    

}

ArticleService:

package com.example.mycnblog.service;

import org.springframework.stereotype.Service;

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

}

4、持久层(mapper)

持久层,具体内容之后完善

在这里插入图片描述
UserMapper:

package com.example.mycnblog.mapper;

import org.apache.ibatis.annotations.Mapper;

/**
 * 用户表的 mapper
 */
@Mapper
public interface UserMapper {
    
    

}

ArticleMapper:

package com.example.mycnblog.mapper;

import org.apache.ibatis.annotations.Mapper;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

}

UserMapper.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.mycnblog.mapper.UserMapper">

</mapper>

ArticleMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mycnblog.mapper.ArticleMapper">

</mapper>

5、工具层(common)

工具层,主要是一些统一处理(返回的数据类型,异常等等)

在这里插入图片描述

① 统一数据返回

AjaxResult:

package com.example.mycnblog.common;

import java.util.HashMap;

/**
 * 自定义的统一返回对象
 */
public class AjaxResult {
    
    

    /**
     * 业务执行成功时,返回的数据格式
     *
     * @param data
     * @return
     */
    public static HashMap<String, Object> success(Object data) {
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "");
        result.put("data", data);
        return result;
    }

    /**
     * 业务执行成功时,返回的数据格式
     *
     * @param msg
     * @param data
     * @return
     */
    public static HashMap<String, Object> success(String msg, Object data) {
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", msg);
        result.put("data", data);
        return result;
    }

    /**
     * 业务执行失败时,返回的数据格式
     *
     * @param code
     * @param msg
     * @return
     */
    public static HashMap<String, Object> fail(int code, String msg) {
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", code);
        result.put("msg", msg);
        result.put("data", "");
        return result;
    }

    /**
     * 业务执行失败时,返回的数据格式
     *
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static HashMap<String, Object> fail(int code, String msg, Object data) {
    
    
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", code);
        result.put("msg", msg);
        result.put("data", data);
        return result;
    }
}

这个地方在做统一返回的时候需要注意一下 String 类的情况

ResponseAdvice:

package com.example.mycnblog.common;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;

/**
 * 统一数据返回封装
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    
    
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    
    
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    

        if(body instanceof HashMap){
    
     // 本身已经是封装好的对象
            return body;
        }

        if (body instanceof String) {
    
     // 返回类型是 string 类
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(AjaxResult.success(body));
        }

        return AjaxResult.success(body);
    }
}

② 统一异常处理

这里还可以具体到各个异常

ExceptionAdvice:

package com.example.mycnblog.common;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 异常类的统一处理
 */
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
    
    

    @ExceptionHandler(Exception.class)
    public Object exceptionAdvice(Exception e) {
    
    
        return AjaxResult.fail(-1, e.getMessage());
    }
}

五、引入前端页面

直接将我们提前准备好的前端页面直接复制粘贴到 /resource/static 目录下:
在这里插入图片描述

六、具体功能实现

1、注册

① 前端页面

reg.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册页面</title>
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/login.css">
    <!-- 引入 jQuery -->
    <script src="js/jquery.min.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="login.html">登陆</a>
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="login-container">
    <!-- 中间的注册框 -->
    <div class="login-dialog">
        <h3>注册</h3>
        <div class="row">
            <span>用户名</span>
            <input type="text" id="username">
        </div>
        <div class="row">
            <span>密码</span>
            <input type="password" id="password">
        </div>
        <div class="row">
            <span>确认密码</span>
            <input type="password" id="password2">
        </div>
        <div class="row">
            <button id="submit" onclick="mysub()">提交</button>
        </div>
    </div>
</div>
</body>
<script>
    function mysub() {
      
      
        // 1.非空效验
        var username = jQuery("#username");
        var password = jQuery("#password");
        var password2 = jQuery("#password2");
        if (username.val() == "") {
      
      
            alert("请先输入用户名!");
            username.focus();
            return false;
        }
        if (password.val() == "") {
      
      
            alert("请先输入密码!");
            password.focus();
            return false;
        }
        if (password2.val() == "") {
      
      
            alert("请先输入确认密码!");
            password2.focus();
            return false;
        }
        if (password.val() != password2.val()) {
      
      
            alert("两次密码输入不一致,请先检查!");
            password.focus();
            return false;
        }
        // 2.发送 Ajax 给后端
        jQuery.ajax({
      
      
            url: "/user/reg",
            type: "POST",
            data: {
      
      
                username: username.val(),
                password: password.val()
            },
            success: function (result) {
      
      
                if (result.code == 200 && result.data == 1) {
      
      
                    alert("恭喜,注册成功!");
                    if (confirm("是否现在就去登录?")) {
      
      
                        location.href = "login.html";
                    }
                } else {
      
      
                    alert("抱歉,注册失败,请重试!");
                }
            }
        })
    }
</script>
</html>

② 后端代码

注册功能,后端只需要将用户名和密码保存到数据库即可,就是一个简单的 insert。

Ⅰ. 数据库

UserMapper:->add

package com.example.mycnblog.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 用户表的 mapper
 */
@Mapper
public interface UserMapper {
    
    

	/**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    public int add(@Param("username") String username,
                   @Param("password") String password);

}

UserMapper.xml:->add

<?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.mycnblog.mapper.UserMapper">
    <insert id="add">
        insert into userinfo(username, password)
        values (#{username}, #{password})
    </insert>
</mapper>

Ⅱ. 服务层

UserService:->add

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户表的服务层
 */
@Service
public class UserService {
    
    

    @Autowired
    private UserMapper userMapper;

	/**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    public int add(String username, String password) {
    
    
        return userMapper.add(username, password);
    }

}

Ⅲ. 控制层

UserController:->reg

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

	/**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1, "非法的参数请求!");
        }
        // 2.进行添加操作
        int result = userService.add(username, password);
        if (result == 1) {
    
    
            return AjaxResult.success("注册成功!", 1);
        } else {
    
    
            return AjaxResult.fail(-1, "数据库添加出错!");
        }
    }
}

③ 效果演示

页面:
输入 -> 张三 123456
在这里插入图片描述
点击提交
在这里插入图片描述

日志信息:
在这里插入图片描述
数据库信息:
在这里插入图片描述

2、登录

① 前端页面

login.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登陆页面</title>
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/login.css">
    <!-- 引入 jQuery -->
    <script src="js/jquery.min.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="reg.html">注册</a>
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="login-container">
    <!-- 中间的登陆框 -->
    <div class="login-dialog">
        <h3>登陆</h3>
        <div class="row">
            <span>用户名</span>
            <input type="text" id="username">
        </div>
        <div class="row">
            <span>密码</span>
            <input type="password" id="password">
        </div>
        <div class="row">
            <button id="submit" onclick="mysub()">提交</button>
        </div>
    </div>
</div>
</body>
<script>
    function mysub() {
      
      
        // 1.非空效验
        var username = jQuery("#username");
        var password = jQuery("#password");
        if (username.val() == "") {
      
      
            alert("请先输入用户名!");
            username.focus();
            return false;
        }
        if (password.val() == "") {
      
      
            alert("请先输入密码!");
            password.focus();
            return false;
        }
        // 2.发送 Ajax 给后端
        jQuery.ajax({
      
      
            url: "/user/login",
            type: "POST",
            data: {
      
      
                username: username.val(),
                password: password.val()
            },
            success: function (result) {
      
      
                if (result.code == 200 && result.data == 1) {
      
      
                    alert("登录成功!");
                    location.href = "myblog_list.html";
                } else {
      
      
                    alert("抱歉,用户名或密码错误,请重试!");
                    username.focus();
                }
            }
        })
    }
</script>
</html>

② 后端代码(基础功能)

登录功能的基础:就是从数据库中查找用户名和密码,与用户输入是否匹配

Ⅰ. 数据库

UserMapper:->login

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 用户表的 mapper
 */
@Mapper
public interface UserMapper {
    
    

    /**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    public int add(@Param("username") String username,
                   @Param("password") String password);

    /**
     * 用户登录
     * @param username
     * @param password
     * @return
     */
    public UserInfo login(@Param("username") String username,
                        @Param("password") String password);
}

UserMapper.xml:->login

<?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.mycnblog.mapper.UserMapper">
    <insert id="add">
        insert into userinfo(username, password)
        values (#{username}, #{password})
    </insert>

    <select id="login" resultType="com.example.mycnblog.model.UserInfo">
        select *
        from userinfo
        where username = #{username}
          and password = #{password}
    </select>
</mapper>

Ⅱ. 服务层

UserService:->login

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.UserMapper;
import com.example.mycnblog.model.UserInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户表的服务层
 */
@Service
public class UserService {
    
    

    @Autowired
    private UserMapper userMapper;

    /**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    public int add(String username, String password) {
    
    
        return userMapper.add(username, password);
    }

    /**
     * 用户登录
     * @param username
     * @param password
     * @return
     */
    public UserInfo login(String username, String password) {
    
    
        return userMapper.login(username, password);
    }

}

Ⅲ. 控制层

UserController:->login

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    /**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1, "非法的参数请求!");
        }
        // 2.进行添加操作
        int result = userService.add(username, password);
        if (result == 1) {
    
    
            return AjaxResult.success("注册成功!", 1);
        } else {
    
    
            return AjaxResult.fail(-1, "数据库添加出错!");
        }
    }

    /**
     * 用户登录
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/login")
    public int login(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            // 参数有误
            return 0;
        }
        // 2.进行查找操作
        UserInfo userInfo = userService.login(username, password);
        if (userInfo == null || userInfo.getId() <= 0) {
    
     // userinfo 无效
            // 用户名或密码错误
            return -1;
        } else {
    
    
        	// 用户名和密码正确
            return 1;
        }
    }
}

③ 效果演示

页面:
输入 -> 张三 123456
在这里插入图片描述
点击提交
在这里插入图片描述
日志信息:
在这里插入图片描述

④ 后端代码(拦截器/存储session)

  • 存储session:需要用一个常量,来存放 session 的 key,我们之后也可以通过这个 key 拿到当前登录用户的信息;我们在存储 session 时,同样也是把 value 存放到对应的 key 中。
  • 拦截器:具体可以看看我之前的博客——SpringBoot 统一功能处理

Ⅰ. 添加常量类

在 common 包下添加一个 Constant 类,用来存放常量

在这里插入图片描述

Constant:

package com.example.mycnblog.common;

/**
 * 当前项目中的所有常量
 */
public class Constant {
    
    
    // 登录信息存储到 session 中的 key
    public static final String SESSION_USERINFO_KEY = "session_userinfo_key";
}

Ⅱ. 控制层

UserController:->login

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    /**
     * 用户注册
     *
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1, "非法的参数请求!");
        }
        // 2.进行添加操作
        int result = userService.add(username, password);
        if (result == 1) {
    
    
            return AjaxResult.success("注册成功!", 1);
        } else {
    
    
            return AjaxResult.fail(-1, "数据库添加出错!");
        }
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return 如果用户名和密码都正确,返回1;如果用户名或密码为空/不正确,返回非1
     */
    @RequestMapping("/login")
    public int login(HttpServletRequest request, String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            // 参数有误
            return 0;
        }
        // 2.进行查找操作
        UserInfo userInfo = userService.login(username, password);
        if (userInfo == null || userInfo.getId() <= 0) {
    
     // userinfo 无效
            // 用户名或密码错误
            return -1;
        } else {
    
    
            // 用户名和密码正确
            // 将 userinfo 保存到 session 中
            HttpSession session = request.getSession();
            session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
            return 1;
        }
    }
}

Ⅲ. 拦截器

在 common 包下添加一个 LoginInterceptor 类,自定义拦截器;以及一个 AppConfig 类,配置拦截器。

在这里插入图片描述
LoginInterceptor:

package com.example.mycnblog.common;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * 自定义拦截器
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        // session 得到用户信息【如果从 session 中得到了 userinfo 对象,说明用户已经登录了,如果没得到,说明未登录】
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            // 当前用户已经登录了
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

AppConfig:->addInterceptors

package com.example.mycnblog.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

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

/**
 * 配置拦截器
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {
    
    

    // 不拦截的 url 集合
    List<String> excludes = new ArrayList<String>(){
    
    {
    
    
        add("/**/*.html");
        add("/js/**"); // 放行 static/js 下的所有文件
        add("/editor.md/**"); // 放行 static/editor.md 下的所有文件
        add("/css/**"); // 放行 static/css 下的所有文件
        add("/img/**"); // 放行 static/img 下的所有文件
        add("/user/reg"); // 放行注册接口
        add("/user/login"); // 放行登录接口
    }};

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        // 配置拦截器
        InterceptorRegistration registration =
                registry.addInterceptor(loginInterceptor);
        registration.addPathPatterns("/**"); // 拦截所有请求
        registration.excludePathPatterns(excludes); // 排除不拦截的 url
    }
}

3、博客列表

① 前端页面

myblog_list.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客列表</title>
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/blog_list.css">
    <!-- 引入 jQuery -->
    <script src="js/jquery.min.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="javascript:onExit()">退出登陆</a>
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="container">
    <!-- 左侧个人信息 -->
    <div class="container-left">
        <div class="card">
            <img src="img/doge.jpg" class="avtar" alt="">
            <h3 id="username"></h3>
            <a href="http:www.github.com">github 地址</a>
            <div class="counter">
                <span>文章</span>
                <span>分类</span>
            </div>
            <div class="counter">
                <span>2</span>
                <span>1</span>
            </div>
        </div>
    </div>
    <!-- 右侧内容详情 -->
    <div class="container-right" id="artlistDiv">
        <!-- 每一篇博客包含标题, 摘要, 时间 -->
        <div class="blog">
            <div class="title">文章标题</div>
            <div class="date">创建时间</div>
            <div class="desc">
                文章内容
            </div>
            <a href="blog_content.html?id=1" class="detail">查看全文 &gt;&gt;</a>
        </div>
    </div>
</div>
</body>

<script>
    var descLength = 80; // 简介的长度

    // 字符串截取,将文章正文截取成简介
    function mySubstr(content) {
      
      

        if (content.length > descLength) {
      
      
            return content.substr(0, descLength);
        }
        return content;
    }

    // 初始化个人列表信息
    function initList() {
      
      
        jQuery.ajax({
      
      
            url: "/art/mylist",
            type: "POST",
            data: {
      
      },
            success: function (result) {
      
      
                console.dir(result);
                if (result.code == 200 && result.data != null && result.data.length > 0) {
      
      
                    // 此人发表文章了
                    var html = "";
                    result.data.forEach(function (item) {
      
      
                        html += '<div class="blog">\n' +
                            '            <div class="title">' + item.title + '</div>\n' +
                            '            <div class="date">' + item.createTime + '</div>\n' +
                            '            <div class="desc">\n' + mySubstr(item.content) + '</div>\n' +
                            '            <div style="margin-left: 200px;margin-top: 50px">' +
                            '               <a href="blog_content.html?id=' + item.id + '">查看详情</a>&nbsp;&nbsp;' +
                            '               <a href="blog_update.html?id=' + item.id + '">修改</a>&nbsp;&nbsp;' +
                            '               <a href="javascript:myDel(' + item.id + ')">删除</a>&nbsp;&nbsp;' +
                            '            </div>' +
                            '        </div>';
                    });
                    jQuery("#artlistDiv").html(html);
                } else {
      
      
                    // 此人未发表任何文章
                    jQuery("#artlistDiv").html("<h1>暂无数据</h1>");
                }
            },
            error: function (err) {
      
      
                if (err != null && err.status == 401) {
      
      
                    alert("用户未登录,即将跳转到登录页")
                    // 已经被拦截器拦截了,未登录
                    location.href = "/login.html";
                }
            }
        });
    }

    initList(); // 当浏览器渲染引擎执行到此行的时候,就会调用 initList() 方法

    // 获取个人信息
    function myinfo() {
      
      
        jQuery.ajax({
      
      
            url: "/user/myinfo",
            type: "POST",
            data: {
      
      },
            success: function (result) {
      
      
                if (result.code == 200 && result.data != null) {
      
      
                    jQuery("#username").text(result.data.username);
                }
            },
            error: function (err) {
      
      
            }
        });
    }

    myinfo();

    // 删除文章
    function myDel(aid) {
      
      
        if (confirm("确认删除?")) {
      
      
            // Ajax 请求后端进行退出操作
            jQuery.ajax({
      
      
                url: "/art/delete",
                type: "POST",
                data: {
      
      
                    "aid": aid
                },
                success: function (result) {
      
      
                    location.href = "/myblog_list.html";
                },
                error: function (err) {
      
      
                    if (err != null && err.status == 401) {
      
      
                        alert("用户未登录,即将跳转到登录页")
                        // 已经被拦截器拦截了,未登录
                        location.href = "/login.html";
                    }
                }
            });
        }
    }

    // 退出登录
    function onExit() {
      
      
        if (confirm("确认退出?")) {
      
      
            // Ajax 请求后端进行退出操作
            jQuery.ajax({
      
      
                url: "/user/logout",
                type: "POST",
                data: {
      
      },
                success: function (result) {
      
      
                    location.href = "/login.html";
                },
                error: function (err) {
      
      
                    if (err != null && err.status == 401) {
      
      
                        alert("用户未登录,即将跳转到登录页")
                        // 已经被拦截器拦截了,未登录
                        location.href = "/login.html";
                    }
                }
            });
        }
    }
</script>
</html>

② 后端代码(退出登录)

退出登录:这个主要就是把 session 中的信息删除。

Ⅰ. 控制层

UserController:->logout

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    /**
     * 用户注册
     *
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1, "非法的参数请求!");
        }
        // 2.进行添加操作
        int result = userService.add(username, password);
        if (result == 1) {
    
    
            return AjaxResult.success("注册成功!", 1);
        } else {
    
    
            return AjaxResult.fail(-1, "数据库添加出错!");
        }
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return 如果用户名和密码都正确,返回1;如果用户名或密码为空/不正确,返回非1
     */
    @RequestMapping("/login")
    public int login(HttpServletRequest request, String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            // 参数有误
            return 0;
        }
        // 2.进行查找操作
        UserInfo userInfo = userService.login(username, password);
        if (userInfo == null || userInfo.getId() <= 0) {
    
     // userinfo 无效
            // 用户名或密码错误
            return -1;
        } else {
    
    
            // 用户名和密码正确
            // 将 userinfo 保存到 session 中
            HttpSession session = request.getSession();
            session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
            return 1;
        }
    }

    /**
     * 退出登录
     * @param request
     * @return
     */
    @RequestMapping("/logout")
    public boolean logout(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            // 移除 session 中当前登录的用户
            session.removeAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return true;
    }
}

Ⅱ. 效果演示

a. 已登录

成功登录后:
在这里插入图片描述
点击退出登录:
在这里插入图片描述
点击确定 -> 来到登录界面:
在这里插入图片描述

b. 未登录

在未登录的情况下,直接进入博客列表,点击退出登录 -> 确定:
在这里插入图片描述
就会来到登录界面:
在这里插入图片描述

③ 后端代码(个人博客显示)

个人博客展示:这个就是在 session 中拿到用户信息,然后根据用户的 id,来查询文章。

Ⅰ. 数据库

ArticleMapper:getMyList

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

    /**
     * 根据用户id(uid)查询所有文章
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(@Param("uid") Integer uid);

}

ArticleMapper.xml:getMyList

<?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.mycnblog.mapper.ArticleMapper">
    <select id="getMyList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where uid = #{
    
    uid}
    </select>
</mapper>

Ⅱ. 服务层

ArticleServic:->getMyList

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.ArticleMapper;
import com.example.mycnblog.model.ArticleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 查询用户的所有文章
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(Integer uid){
    
    
        return articleMapper.getMyList(uid);
    }

}

Ⅲ. 控制层

ArticleController:->myList

package com.example.mycnblog.controller;

import com.example.mycnblog.common.Constant;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            UserInfo userInfo = (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

}

Ⅳ. 效果演示

页面:
在这里插入图片描述

日志信息:
在这里插入图片描述
数据库信息:
在这里插入图片描述

④ 后端代码(个人信息)

个人信息:这个跟博客展示其实差不多,直接在 session 中就可以拿到信息。

Ⅰ. 控制层

UserController:->myInfo

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    /**
     * 用户注册
     *
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1, "非法的参数请求!");
        }
        // 2.进行添加操作
        int result = userService.add(username, password);
        if (result == 1) {
    
    
            return AjaxResult.success("注册成功!", 1);
        } else {
    
    
            return AjaxResult.fail(-1, "数据库添加出错!");
        }
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return 如果用户名和密码都正确,返回1;如果用户名或密码为空/不正确,返回非1
     */
    @RequestMapping("/login")
    public int login(HttpServletRequest request, String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            // 参数有误
            return 0;
        }
        // 2.进行查找操作
        UserInfo userInfo = userService.login(username, password);
        if (userInfo == null || userInfo.getId() <= 0) {
    
     // userinfo 无效
            // 用户名或密码错误
            return -1;
        } else {
    
    
            // 用户名和密码正确
            // 将 userinfo 保存到 session 中
            HttpSession session = request.getSession();
            session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
            return 1;
        }
    }

    /**
     * 退出登录
     *
     * @param request
     * @return
     */
    @RequestMapping("/logout")
    public boolean logout(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            // 移除 session 中当前登录的用户
            session.removeAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return true;
    }

    /**
     * 获取个人信息
     * @param request
     * @return
     */
    @RequestMapping("/myinfo")
    public UserInfo myInfo(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            return (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return null;
    }
}

Ⅱ. 效果演示

在这里插入图片描述

4、文章查询

① 前端页面

这里的 tools.js 是因为这个功能在很多地方都用到了,所以封装成 js 工具类。

tools.js:

// 获取当前 url 中某个参数的方法
function getURLParam(key) {
    
    
    var params = location.search;
    if (params.indexOf("?") >= 0) {
    
    
        params = params.substring(params.indexOf("?") + 1);
        var paramArr = params.split('&');
        for (var i = 0; i < paramArr.length; i++) {
    
    
            var namevalues = paramArr[i].split("=");
            if (namevalues[0] == key) {
    
    
                return namevalues[1];
            }
        }
    } else {
    
    
        return "";
    }
}

blog_content.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客正文</title>
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/blog_content.css">
    <link rel="stylesheet" href="editor.md/css/editormd.preview.min.css"/>
    <script src="js/jquery.min.js"></script>
    <script src="editor.md/editormd.js"></script>
    <script src="editor.md/lib/marked.min.js"></script>
    <script src="editor.md/lib/prettify.min.js"></script>
    <script src="js/tools.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="myblog_list.html">我的</a>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="login.html">登陆</a>
</div>
<!-- 版心 -->
<div class="container">
    <!-- 左侧个人信息 -->
    <div class="container-left">
        <div class="card">
            <img src="img/doge.jpg" class="avtar" alt="">
            <h3 id="username">用户名称</h3>
            <a href="http:www.github.com">github 地址</a>
            <div class="counter">
                <span>文章</span>
                <span>分类</span>
            </div>
            <div class="counter">
                <span>2</span>
                <span>1</span>
            </div>
        </div>
    </div>
    <!-- 右侧内容详情 -->
    <div class="container-right">
        <div class="blog-content">
            <!-- 博客标题 -->
            <h3 id="title">标题</h3>
            <!-- 博客时间 -->
            <div class="date">
                <span id="data"></span> &nbsp;&nbsp;&nbsp;&nbsp;
                访问量:<span id="rcount"></span> &nbsp;&nbsp;&nbsp;&nbsp;
            </div>
            <!-- 博客正文 -->
            <div id="editorDiv">

            </div>
        </div>
    </div>
</div>
<script type="text/javascript">
    var editormd;

    function initEdit(md) {
      
      
        editormd = editormd.markdownToHTML("editorDiv", {
      
      
            markdown: md, // Also, you can dynamic set Markdown text
            // htmlDecode : true,  // Enable / disable HTML tag encode.
            // htmlDecode : "style,script,iframe",  // Note: If enabled, you should filter some dangerous HTML tags for website security.
        });
    }

    // 获取个人信息
    function myInfo(uid) {
      
      
        // 后端添加 /user/myinfobyuid 通过用户 id 查询用户对象
        jQuery.ajax({
      
      
            url: "/user/myinfobyuid",
            type: "POST",
            data: {
      
      "uid": uid},
            success: function (result) {
      
      
                if (result.code == 200 && result.data != null) {
      
      
                    jQuery("#username").text(result.data.username);
                }
            },
            error: function (err) {
      
      

            }
        });
    }

    // 获取文章的详情信息
    function getArtDetail() {
      
      
        // 从 url 中获取到文章id
        var aid = getURLParam("id");
        if (aid != null && aid > 0) {
      
      
            // 访问后端查询文章详情
            jQuery.ajax({
      
      
                url: "/art/detail",
                type: "POST",
                data: {
      
      "aid": aid},
                success: function (result) {
      
      
                    if (result.code == 200 && result.data != null) {
      
      
                        var art = result.data;
                        jQuery("#title").text(art.title);
                        jQuery("#data").text(art.createTime);
                        jQuery("#rcount").text(art.rcount);
                        editormd = editormd.markdownToHTML("editorDiv", {
      
      
                            markdown: art.content
                        });
                        myInfo(art.uid);
                    }
                }
            });
        }
    }

    getArtDetail();
</script>
</body>

</html>

② 后端代码(文章展示)

文章展示:通过文章 id 查询文章的详细信息,值得注意的就是后面我们在全部博客列表中,也会用到这个功能,所以我们需要在拦截器中放行该接口。

Ⅰ. 数据库

ArticleMapper:->getDetil

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

    /**
     * 根据用户id(uid)查询所有文章
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(@Param("uid") Integer uid);

    /**
     * 根据文章id(id)获取文章的详情信息
     * @param aid
     * @return
     */
    public ArticleInfo getDetail(@Param("aid") Integer aid);
}

ArticleMapper.xml:->getDetil

<?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.mycnblog.mapper.ArticleMapper">
    <select id="getMyList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where uid = #{
    
    uid}
    </select>

    <select id="getDetail" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where id = #{
    
    aid}
    </select>
</mapper>

Ⅱ. 服务层

ArticleService:->getDetil

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.ArticleMapper;
import com.example.mycnblog.model.ArticleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 查询用户的所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(Integer uid) {
    
    
        return articleMapper.getMyList(uid);
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetil(Integer aid) {
    
    
        return articleMapper.getDetail(aid);
    }
}

Ⅲ. 控制层

ArticleController:->getDetil

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     *
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            UserInfo userInfo = (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public Object getDetil(Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            return AjaxResult.success(articleService.getDetil(aid));
        }
        return AjaxResult.fail(-1, "查询失败");
    }
}

Ⅳ. 拦截器

涉及到之后,我们可能在所有文章列表中也需要,查询文章详情,所以直接把这个接口放行。

AppConfig:->放行文章详情接口

package com.example.mycnblog.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

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

/**
 * 配置拦截器
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {
    
    

    // 不拦截的 url 集合
    List<String> excludes = new ArrayList<String>(){
    
    {
    
    
        add("/**/*.html");
        add("/js/**"); // 放行 static/js 下的所有文件
        add("/editor.md/**"); // 放行 static/editor.md 下的所有文件
        add("/css/**"); // 放行 static/css 下的所有文件
        add("/img/**"); // 放行 static/img 下的所有文件
        add("/user/reg"); // 放行注册接口
        add("/user/login"); // 放行登录接口
        add("/art/detail"); // 放行文章详情接口
    }};

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        // 配置拦截器
        InterceptorRegistration registration =
                registry.addInterceptor(loginInterceptor);
        registration.addPathPatterns("/**"); // 拦截所有请求
        registration.excludePathPatterns(excludes); // 排除不拦截的 url
    }
}

Ⅴ. 效果演示

进入个人博客页面,点击查看详情:
在这里插入图片描述
就得到了文章的详情内容:
在这里插入图片描述

③ 后端代码(作者信息)

作者信息:在文章展示的同时,前端可以获取到文章的 uid,然后后端就可以通过这个 uid 查询作者信息。

Ⅰ. 数据库

UserMapper:->myInfoByUid

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 用户表的 mapper
 */
@Mapper
public interface UserMapper {
    
    

    /**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    public int add(@Param("username") String username,
                   @Param("password") String password);

    /**
     * 用户登录
     * @param username
     * @param password
     * @return
     */
    public UserInfo login(@Param("username") String username,
                        @Param("password") String password);

    /**
     * 通过用户 id 查询用户信息
     * @param uid
     * @return
     */
    public UserInfo myInfoByUid(@Param("uid") Integer uid);
}

UserMapper.xml:->myInfoByUid

<?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.mycnblog.mapper.UserMapper">
    <insert id="add">
        insert into userinfo(username, password)
        values (#{
    
    username}, #{
    
    password})
    </insert>

    <select id="login" resultType="com.example.mycnblog.model.UserInfo">
        select *
        from userinfo
        where username = #{
    
    username}
          and password = #{
    
    password}
    </select>

    <select id="myInfoByUid" resultType="com.example.mycnblog.model.UserInfo">
        select *
        from userinfo
        where id = #{
    
    uid}
    </select>
</mapper>

Ⅱ. 服务层

UserService:->myInfoByUid

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.UserMapper;
import com.example.mycnblog.model.UserInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户表的服务层
 */
@Service
public class UserService {
    
    

    @Autowired
    private UserMapper userMapper;

    /**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    public int add(String username, String password) {
    
    
        return userMapper.add(username, password);
    }

    /**
     * 用户登录
     * @param username
     * @param password
     * @return
     */
    public UserInfo login(String username, String password) {
    
    
        return userMapper.login(username, password);
    }

    /**
     * 获取作者信息
     * @param uid
     * @return
     */
    public UserInfo myInfoByUid(Integer uid){
    
    
        return userMapper.myInfoByUid(uid);
    }
}

Ⅲ. 控制层

UserController:->myInfoByUid

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    /**
     * 用户注册
     *
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1, "非法的参数请求!");
        }
        // 2.进行添加操作
        int result = userService.add(username, password);
        if (result == 1) {
    
    
            return AjaxResult.success("注册成功!", 1);
        } else {
    
    
            return AjaxResult.fail(-1, "数据库添加出错!");
        }
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return 如果用户名和密码都正确,返回1;如果用户名或密码为空/不正确,返回非1
     */
    @RequestMapping("/login")
    public int login(HttpServletRequest request, String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            // 参数有误
            return 0;
        }
        // 2.进行查找操作
        UserInfo userInfo = userService.login(username, password);
        if (userInfo == null || userInfo.getId() <= 0) {
    
     // userinfo 无效
            // 用户名或密码错误
            return -1;
        } else {
    
    
            // 用户名和密码正确
            // 将 userinfo 保存到 session 中
            HttpSession session = request.getSession();
            session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
            return 1;
        }
    }

    /**
     * 退出登录
     *
     * @param request
     * @return
     */
    @RequestMapping("/logout")
    public boolean logout(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            // 移除 session 中当前登录的用户
            session.removeAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return true;
    }

    /**
     * 获取个人信息
     *
     * @param request
     * @return
     */
    @RequestMapping("/myinfo")
    public UserInfo myInfo(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            return (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return null;
    }

    /**
     * 获取作者信息
     * @param uid
     * @return
     */
    @RequestMapping("/myinfobyuid")
    public UserInfo myInfoByUid(Integer uid) {
    
    
        if (uid != null && uid > 0) {
    
    
            return userService.myInfoByUid(uid);
        }
        return null;
    }
}

Ⅳ. 效果演示

在这里插入图片描述

5、文章修改

① 前端页面

blog_update.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客编辑</title>

    <!-- 引入自己写的样式 -->
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/blog_edit.css">

    <!-- 引入 editor.md 的依赖 -->
    <link rel="stylesheet" href="editor.md/css/editormd.min.css"/>
    <script src="js/jquery.min.js"></script>
    <script src="editor.md/editormd.js"></script>

    <!-- 引入工具js -->
    <script src="js/tools.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="#">退出登录</a>
</div>
<!-- 编辑框容器 -->
<div class="blog-edit-container">
    <!-- 标题编辑区 -->
    <div class="title">
        <input id="title" type="text" placeholder="在这里写下文章标题">
        <button onclick="mysub()">修改文章</button>
    </div>
    <!-- 创建编辑器标签 -->
    <div id="editorDiv">
        <textarea id="editor-markdown" style="display:none;"></textarea>
    </div>
</div>

<script>
    var aid;
    var editor;

    function initEdit(md) {
      
      
        // 编辑器设置
        editor = editormd("editorDiv", {
      
      
            // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
            width: "100%",
            // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
            height: "calc(100% - 50px)",
            // 编辑器中的初始内容
            markdown: md,
            // 指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",
            saveHTMLToTextarea: true //
        });
    }

    // 提交
    function mysub() {
      
      
        // alert(editor.getValue()); // 获取值
        var title = jQuery("#title");
        var content = editor.getValue();
        // 非空效验
        if (title.val() == "") {
      
      
            title.focus();
            alert("请先输入标题!");
            return false;
        }
        if (content == "") {
      
      
            content.focus();
            alert("请先输入正文!");
            return false;
        }
        jQuery.ajax({
      
      
            url: "/art/update",
            type: "POST",
            data: {
      
      
                "aid": aid,
                "title": title.val(),
                "content": content
            },
            success: function (result) {
      
      
                if (result.code == 200 && result.data > 0) {
      
      
                    alert("恭喜:修改成功!")
                    location.href = "myblog_list.html";
                } else {
      
      
                    alert("抱歉:修改失败,请重试!");
                }
            },
            error: function (err) {
      
      
                if (err != null && err.status == 401) {
      
      
                    alert("用户未登录,即将跳转到登录页!");
                    // 已经被拦截器拦截了,未登录
                    location.href = "/login.html";
                }
            }
        });
    }

    // 查询文章详情并展现
    function showArt() {
      
      
        // 从 url 中获取文章 id
        aid = getURLParam("id");
        if (aid != null && aid > 0) {
      
      
            // 访问后端查询文章详情
            jQuery.ajax({
      
      
                url: "/art/detailbyid",
                type: "POST",
                data: {
      
      "aid": aid},
                success: function (result) {
      
      
                    if (result.code == 200 && result.data != null) {
      
      
                        var art = result.data;
                        jQuery("#title").val(art.title);
                        initEdit(art.content);
                    } else {
      
      
                        alert("查询失败,请重试!");
                    }
                },
                error: function (err) {
      
      
                    if (err != null && err.status == 401) {
      
      
                        alert("用户未登录,即将跳转到登录页!");
                        // 已经被拦截器拦截了,未登录
                        location.href = "/login.html";
                    }
                }
            });
        }
    }

    showArt();
</script>
</body>

</html>

② 后端代码(查询文章)

查询文章:这个就是通过文章 id 查询文章内容,这里的数据库和服务层可以用我们之前已经实现好的 getDetil;同时还有进行一次文章的归属人验证,这里就需要用到获取 session 信息,之前 myList 这个接口也用到了,所以我们就可以把它封装起来。

Ⅰ. 工具层

SessionUtil:->getLoginUser

package com.example.mycnblog.common;

import com.example.mycnblog.model.UserInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class SessionUtil {
    
    

    /**
     * 查询当前登录用户的 session 信息
     * @param request
     * @return
     */
    public static UserInfo getLoginUser(HttpServletRequest request){
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            return (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return null;
    }
}

Ⅱ. 控制层

ArticleController:->getDetailById

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.common.SessionUtil;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     *
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
    
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public Object getDetil(Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            return AjaxResult.success(articleService.getDetil(aid));
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,查询文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/detailbyid")
    public Object getDetailById(HttpServletRequest request, Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            // 根据文章查询文章的详情
            ArticleInfo articleInfo = articleService.getDetil(aid);
            // 文章的归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if (userInfo != null && articleInfo != null &&
                    userInfo.getId() == articleInfo.getUid()) {
    
     // 文章归属人正确
                return AjaxResult.success(articleInfo);
            }
        }
        return AjaxResult.fail(-1, "查询失败");
    }
}

Ⅲ. 效果演示

进入个人博客页面,点击修改:
在这里插入图片描述
就到了修改界面:
在这里插入图片描述

③ 后端代码(修改文章)

Ⅰ. 数据库

ArticleMapper:->update

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

    /**
     * 根据用户id(uid)查询所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(@Param("uid") Integer uid);

    /**
     * 根据文章id(id)获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetail(@Param("aid") Integer aid);

    /**
     * 根据文章id(id)和用户id(uid)修改文章的标题和内容
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(@Param("aid") Integer aid,
                      @Param("uid") Integer uid,
                      @Param("title") String title,
                      @Param("content") String content);
}

ArticleMapper.xml:->update

<?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.mycnblog.mapper.ArticleMapper">
    <select id="getMyList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where uid = #{uid}
    </select>

    <select id="getDetail" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where id = #{aid}
    </select>

    <update id="update">
        update articleinfo
        set title=#{title},
            content=#{content}
        where id = #{aid}
          and uid = #{uid}
    </update>
</mapper>

Ⅱ. 服务层

ArticleService:->update

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.ArticleMapper;
import com.example.mycnblog.model.ArticleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 查询用户的所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(Integer uid) {
    
    
        return articleMapper.getMyList(uid);
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetil(Integer aid) {
    
    
        return articleMapper.getDetail(aid);
    }

    /**
     * 修改页面,修改文章
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(Integer aid, Integer uid, String title, String content) {
    
    
        return articleMapper.update(aid, uid, title, content);
    }
}

Ⅲ. 控制层

ArticleController:->update

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.common.SessionUtil;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     *
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
    
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public Object getDetil(Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            return AjaxResult.success(articleService.getDetil(aid));
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,查询文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/detailbyid")
    public Object getDetailById(HttpServletRequest request, Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            // 根据文章查询文章的详情
            ArticleInfo articleInfo = articleService.getDetil(aid);
            // 文章的归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if (userInfo != null && articleInfo != null &&
                    userInfo.getId() == articleInfo.getUid()) {
    
     // 文章归属人正确
                return AjaxResult.success(articleInfo);
            }
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,修改文章
     * @param request
     * @param aid
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/update")
    public int update(HttpServletRequest request, Integer aid, String title, String content) {
    
    
        // 非空校验
        if (aid == null || !StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.update(aid, userInfo.getId(), title, content);
        }
        return 0;
    }
}

Ⅳ. 效果演示

修改前数据库信息:
在这里插入图片描述
进入修改页面,做出修改,点击修改文章:
在这里插入图片描述
提示修改成功:
在这里插入图片描述
跳转至个人博客列表:
在这里插入图片描述
修改后数据库信息:
在这里插入图片描述

6、文章删除

① 前端页面

myblog_list.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客列表</title>
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/blog_list.css">
    <!-- 引入 jQuery -->
    <script src="js/jquery.min.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="javascript:onExit()">退出登陆</a>
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="container">
    <!-- 左侧个人信息 -->
    <div class="container-left">
        <div class="card">
            <img src="img/doge.jpg" class="avtar" alt="">
            <h3 id="username"></h3>
            <a href="http:www.github.com">github 地址</a>
            <div class="counter">
                <span>文章</span>
                <span>分类</span>
            </div>
            <div class="counter">
                <span>2</span>
                <span>1</span>
            </div>
        </div>
    </div>
    <!-- 右侧内容详情 -->
    <div class="container-right" id="artlistDiv">
        <!-- 每一篇博客包含标题, 摘要, 时间 -->
        <div class="blog">
            <div class="title">文章标题</div>
            <div class="date">创建时间</div>
            <div class="desc">
                文章内容
            </div>
            <a href="blog_content.html?id=1" class="detail">查看全文 &gt;&gt;</a>
        </div>
    </div>
</div>
</body>

<script>
    var descLength = 80; // 简介的长度

    // 字符串截取,将文章正文截取成简介
    function mySubstr(content) {
      
      

        if (content.length > descLength) {
      
      
            return content.substr(0, descLength);
        }
        return content;
    }

    // 初始化个人列表信息
    function initList() {
      
      
        jQuery.ajax({
      
      
            url: "/art/mylist",
            type: "POST",
            data: {
      
      },
            success: function (result) {
      
      
                console.dir(result);
                if (result.code == 200 && result.data != null && result.data.length > 0) {
      
      
                    // 此人发表文章了
                    var html = "";
                    result.data.forEach(function (item) {
      
      
                        html += '<div class="blog">\n' +
                            '            <div class="title">' + item.title + '</div>\n' +
                            '            <div class="date">' + item.createTime + '</div>\n' +
                            '            <div class="desc">\n' + mySubstr(item.content) + '</div>\n' +
                            '            <div style="margin-left: 200px;margin-top: 50px">' +
                            '               <a href="blog_content.html?id=' + item.id + '">查看详情</a>&nbsp;&nbsp;' +
                            '               <a href="blog_update.html?id=' + item.id + '">修改</a>&nbsp;&nbsp;' +
                            '               <a href="javascript:myDel(' + item.id + ')">删除</a>&nbsp;&nbsp;' +
                            '            </div>' +
                            '        </div>';
                    });
                    jQuery("#artlistDiv").html(html);
                } else {
      
      
                    // 此人未发表任何文章
                    jQuery("#artlistDiv").html("<h1>暂无数据</h1>");
                }
            },
            error: function (err) {
      
      
                if (err != null && err.status == 401) {
      
      
                    alert("用户未登录,即将跳转到登录页")
                    // 已经被拦截器拦截了,未登录
                    location.href = "/login.html";
                }
            }
        });
    }

    initList(); // 当浏览器渲染引擎执行到此行的时候,就会调用 initList() 方法

    // 获取个人信息
    function myinfo() {
      
      
        jQuery.ajax({
      
      
            url: "/user/myinfo",
            type: "POST",
            data: {
      
      },
            success: function (result) {
      
      
                if (result.code == 200 && result.data != null) {
      
      
                    jQuery("#username").text(result.data.username);
                }
            },
            error: function (err) {
      
      
            }
        });
    }

    myinfo();

    // 删除文章
    function myDel(aid) {
      
      
        if (confirm("确认删除?")) {
      
      
            // Ajax 请求后端进行退出操作
            jQuery.ajax({
      
      
                url: "/art/delete",
                type: "POST",
                data: {
      
      
                    "aid": aid
                },
                success: function (result) {
      
      
                    location.href = "/myblog_list.html";
                },
                error: function (err) {
      
      
                    if (err != null && err.status == 401) {
      
      
                        alert("用户未登录,即将跳转到登录页")
                        // 已经被拦截器拦截了,未登录
                        location.href = "/login.html";
                    }
                }
            });
        }
    }

    // 退出登录
    function onExit() {
      
      
        if (confirm("确认退出?")) {
      
      
            // Ajax 请求后端进行退出操作
            jQuery.ajax({
      
      
                url: "/user/logout",
                type: "POST",
                data: {
      
      },
                success: function (result) {
      
      
                    location.href = "/login.html";
                },
                error: function (err) {
      
      
                    if (err != null && err.status == 401) {
      
      
                        alert("用户未登录,即将跳转到登录页")
                        // 已经被拦截器拦截了,未登录
                        location.href = "/login.html";
                    }
                }
            });
        }
    }
</script>
</html>

② 后端代码

文章删除:这个其实很简单,对于后端来说,就是一个简单的通过文章 id 删除文章。

Ⅰ. 数据库

ArticleMapper:->delete

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

    /**
     * 根据用户id(uid)查询所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(@Param("uid") Integer uid);

    /**
     * 根据文章id(id)获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetail(@Param("aid") Integer aid);

    /**
     * 根据文章id(id)和用户id(uid)修改文章的标题和内容
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(@Param("aid") Integer aid,
                      @Param("uid") Integer uid,
                      @Param("title") String title,
                      @Param("content") String content);

    /**
     * 根据文章id(id)删除文章
     *
     * @param aid
     * @return
     */
    public int delete(@Param("aid") Integer aid);
}

ArticleMapper.xml:->delete

<?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.mycnblog.mapper.ArticleMapper">
    <select id="getMyList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where uid = #{uid}
    </select>

    <select id="getDetail" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where id = #{aid}
    </select>

    <update id="update">
        update articleinfo
        set title=#{title},
            content=#{content}
        where id = #{aid}
          and uid = #{uid}
    </update>

    <delete id="delete">
        delete
        from articleinfo
        where id = #{aid}
    </delete>
</mapper>

Ⅱ. 服务层

ArticleService:->delete

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.ArticleMapper;
import com.example.mycnblog.model.ArticleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 用户博客列表,查询用户的所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(Integer uid) {
    
    
        return articleMapper.getMyList(uid);
    }

    /**
     * 用户博客列表,获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetil(Integer aid) {
    
    
        return articleMapper.getDetail(aid);
    }

    /**
     * 修改页面,修改文章
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(Integer aid, Integer uid, String title, String content) {
    
    
        return articleMapper.update(aid, uid, title, content);
    }

    /**
     * 用户博客列表,删除文章
     *
     * @param aid
     * @return
     */
    public int delete(Integer aid) {
    
    
        return articleMapper.delete(aid);
    }
}

Ⅲ. 控制层

ArticleController:->delete

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.common.SessionUtil;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     *
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
    
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public Object getDetil(Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            return AjaxResult.success(articleService.getDetil(aid));
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,查询文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/detailbyid")
    public Object getDetailById(HttpServletRequest request, Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            // 根据文章查询文章的详情
            ArticleInfo articleInfo = articleService.getDetil(aid);
            // 文章的归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if (userInfo != null && articleInfo != null &&
                    userInfo.getId() == articleInfo.getUid()) {
    
     // 文章归属人正确
                return AjaxResult.success(articleInfo);
            }
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,修改文章
     *
     * @param request
     * @param aid
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/update")
    public int update(HttpServletRequest request, Integer aid, String title, String content) {
    
    
        // 非空校验
        if (aid == null || !StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.update(aid, userInfo.getId(), title, content);
        }
        return 0;
    }

    /**
     * 博客列表,删除文章
     * 
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/delete")
    public int delete(HttpServletRequest request, Integer aid) {
    
    
        // 非空校验
        if (aid == null && aid <= 0) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
     // 当前已登录
            return articleService.delete(aid);
        }
        return 0;
    }
}

③ 效果演示

删除前数据库信息:
在这里插入图片描述
登录后,进入博客列表:
在这里插入图片描述
点击删除:
在这里插入图片描述
确定删除:
在这里插入图片描述
删除后数据库信息:
在这里插入图片描述

7、发布文章

① 前端页面

blog_edit.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客编辑</title>

    <!-- 引入自己写的样式 -->
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/blog_edit.css">

    <!-- 引入 editor.md 的依赖 -->
    <link rel="stylesheet" href="editor.md/css/editormd.min.css"/>
    <script src="js/jquery.min.js"></script>
    <script src="editor.md/editormd.js"></script>

    <!-- 引入工具js -->
    <script src="js/tools.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="#">退出登录</a>
</div>
<!-- 编辑框容器 -->
<div class="blog-edit-container">
    <!-- 标题编辑区 -->
    <div class="title">
        <input id="title" type="text" placeholder="在这里写下文章标题">
        <button onclick="mysub()">发布文章</button>
    </div>
    <!-- 创建编辑器标签 -->
    <div id="editorDiv">
        <textarea id="editor-markdown" style="display:none;"></textarea>
    </div>
</div>

<script>
    var editor;

    function initEdit(md) {
      
      
        // 编辑器设置
        editor = editormd("editorDiv", {
      
      
            // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
            width: "100%",
            // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
            height: "calc(100% - 50px)",
            // 编辑器中的初始内容
            markdown: md,
            // 指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",
            saveHTMLToTextarea: true //
        });
    }

    initEdit("# 在这里写下一篇博客"); // 初始化编译器的值
    // 提交
    function mysub() {
      
      
        // alert(editor.getValue()); // 获取值
        // editor.setValue("#123") // 设置值
        var title = jQuery("#title");
        var content = editor.getValue();
        // 非空效验
        if (title.val() == "") {
      
      
            title.focus();
            alert("请先输入标题!");
            return false;
        }
        if (content == "") {
      
      
            content.focus();
            alert("请先输入正文!");
            return false;
        }
        jQuery.ajax({
      
      
            url: "/art/add",
            type: "POST",
            data: {
      
      
                "title": title.val(),
                "content": content
            },
            success: function (result) {
      
      
                if (result.code == 200 && result.data > 0) {
      
      
                    alert("恭喜:发布成功!")
                    location.href = "myblog_list.html";
                } else {
      
      
                    alert("抱歉:发布失败,请重试!");
                }
            },
            error: function (err) {
      
      
                if (err != null && err.status == 401) {
      
      
                    alert("用户未登录,即将跳转到登录页!");
                    // 已经被拦截器拦截了,未登录
                    location.href = "/login.html";
                }
            }
        });
    }
</script>
</body>

</html>

② 后端代码

发布文章:这个它其实和修改文章差不多,只是不需要获取文章的 id了,这个在发布时,数据库会自己自增 id。

Ⅰ. 数据库

ArticleMapper:->add

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

    /**
     * 根据用户id(uid)查询所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(@Param("uid") Integer uid);

    /**
     * 根据文章id(id)获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetail(@Param("aid") Integer aid);

    /**
     * 根据文章id(id)和用户id(uid)修改文章的标题和内容
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(@Param("aid") Integer aid,
                      @Param("uid") Integer uid,
                      @Param("title") String title,
                      @Param("content") String content);

    /**
     * 根据文章id(id)删除文章
     *
     * @param aid
     * @return
     */
    public int delete(@Param("aid") Integer aid);

    /**
     * 添加文章(uid,文章标题,文章内容)
     *
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int add(@Param("uid") Integer uid,
                   @Param("title") String title,
                   @Param("content") String content);
}

ArticleMapper.xml:->add

<?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.mycnblog.mapper.ArticleMapper">
    <select id="getMyList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where uid = #{uid}
    </select>

    <select id="getDetail" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where id = #{aid}
    </select>

    <update id="update">
        update articleinfo
        set title=#{title},
            content=#{content}
        where id = #{aid}
          and uid = #{uid}
    </update>

    <delete id="delete">
        delete
        from articleinfo
        where id = #{aid}
    </delete>

    <insert id="add">
        insert into articleinfo(title, content, uid)
        values (#{title}, #{content}, #{uid});
    </insert>
</mapper>

Ⅱ. 服务层

ArticleService:->add

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.ArticleMapper;
import com.example.mycnblog.model.ArticleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 用户博客列表,查询用户的所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(Integer uid) {
    
    
        return articleMapper.getMyList(uid);
    }

    /**
     * 用户博客列表,获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetil(Integer aid) {
    
    
        return articleMapper.getDetail(aid);
    }

    /**
     * 修改页面,修改文章
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(Integer aid, Integer uid, String title, String content) {
    
    
        return articleMapper.update(aid, uid, title, content);
    }

    /**
     * 用户博客列表,删除文章
     *
     * @param aid
     * @return
     */
    public int delete(Integer aid) {
    
    
        return articleMapper.delete(aid);
    }

    /**
     * 博客编辑,发布文章
     *
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int add(Integer uid, String title, String content) {
    
    
        return articleMapper.add(uid, title, content);
    }
}

Ⅲ. 控制层

ArticleController:->add

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.common.SessionUtil;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 1. 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     *
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
    
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public Object getDetil(Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            return AjaxResult.success(articleService.getDetil(aid));
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,查询文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/detailbyid")
    public Object getDetailById(HttpServletRequest request, Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            // 根据文章查询文章的详情
            ArticleInfo articleInfo = articleService.getDetil(aid);
            // 文章的归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if (userInfo != null && articleInfo != null &&
                    userInfo.getId() == articleInfo.getUid()) {
    
     // 文章归属人正确
                return AjaxResult.success(articleInfo);
            }
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,修改文章
     *
     * @param request
     * @param aid
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/update")
    public int update(HttpServletRequest request, Integer aid, String title, String content) {
    
    
        // 非空校验
        if (aid == null || !StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.update(aid, userInfo.getId(), title, content);
        }
        return 0;
    }

    /**
     * 博客列表,删除文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/delete")
    public int delete(HttpServletRequest request, Integer aid) {
    
    
        // 非空校验
        if (aid == null && aid <= 0) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
     // 当前已登录
            return articleService.delete(aid);
        }
        return 0;
    }

    /**
     * 博客编辑,发布文章
     *
     * @param request
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/add")
    public int add(HttpServletRequest request, String title, String content) {
    
    
        // 非空校验
        if (!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.add(userInfo.getId(), title, content);
        }
        return 0;
    }
}

③ 效果演示

进入个人博客列表:
在这里插入图片描述
点击写博客:
在这里插入图片描述
写一些内容点击,发布文章:
在这里插入图片描述
点击确定,发布文章成功:
在这里插入图片描述
发布前数据库信息:
在这里插入图片描述
发布后数据库信息:
在这里插入图片描述

8、文章分页列表

① 前端页面

blog_list.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>博客列表</title>
    <link rel="stylesheet" href="css/list.css">
    <link rel="stylesheet" href="css/blog_list.css">
    <style>

        .nav {
      
      
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            height: 50px;
        }

        .container {
      
      
            padding-top: 80px;
            height: auto;
        }

        .container-right {
      
      
            width: auto;
        }

        .blog-pagnation-wrapper {
      
      
            height: 40px;
            margin: 16px 0;
            text-align: center;
        }

        .blog-pagnation-item {
      
      
            display: inline-block;
            padding: 8px;
            border: 1px solid #d0d0d5;
            color: #333;
        }

        .blog-pagnation-item:hover {
      
      
            background: #4e4eeb;
            color: #fff;
        }

        .blog-pagnation-item.actvie {
      
      
            background: #4e4eeb;
            color: #fff;
        }

    </style>

    <!-- 引入 jQuery -->
    <script src="js/jquery.min.js"></script>
    <!-- 引入工具js -->
    <script src="js/tools.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="myblog_list.html">我的</a>
    <a href="blog_list.html">主页</a>
    <a href="blog_edit.html">写博客</a>
    <a href="login.html">登陆</a>
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="container">
    <!-- 右侧内容详情 -->
    <div class="container-right" style="width: 100%">
        <div id="listDiv">
            <!-- 每一篇博客包含标题, 摘要, 时间 -->
            <div class="blog">
                <div class="title">文章标题</div>
                <div class="date">创建时间</div>
                <div class="desc">正文</div>
                <a href="blog_content.html?id=1" class="detail">查看全文</a>
            </div>
        </div>
        <hr>
        <div class="blog-pagnation-wrapper">
            <button class="blog-pagnation-item" onclick="firstClick()">首页</button>
            <button class="blog-pagnation-item" onclick="beforeClick()">上一页</button>
            <button class="blog-pagnation-item" onclick="nextClick()">下一页</button>
            <button class="blog-pagnation-item" onclick="lastClick()">末页</button>
        </div>
    </div>
</div>
<script>
    var PageIndex = 1; // 当前的页码
    var PageSize = 3; // 每页最大容量
    var TotalPage = 1; // 总页数

    // 初始化分页的参数,尝试从 url 中 获取 PageIndex 和 PageSize
    function initPageParam() {
      
      
        var pi = getURLParam("PageIndex");
        if (pi != "") {
      
      
            PageIndex = pi;
        }
        var ps = getURLParam("PageSize");
        if (ps != "") {
      
      
            PageSize = ps;
        }
    }

    initPageParam();

    // 查询总共有多少页的数据
    function getTotalPage() {
      
      
        jQuery.ajax({
      
      
            url: "/art/totalpage",
            type: "GET",
            data: {
      
      
                "PageSize": PageSize
            },
            success: function (result) {
      
      
                if (result.code == 200 && result.data != null) {
      
      
                    TotalPage = result.data;
                }
            }
        });
    }

    getTotalPage();

    // 查询分页数据
    function getList() {
      
      
        jQuery.ajax({
      
      
            url: "/art/list",
            type: "GET",
            data: {
      
      
                "PageIndex": PageIndex,
                "PageSize": PageSize
            },
            success: function (result) {
      
      
                if (result.code == 200 && result.data != null && result.data.length > 0) {
      
      
                    // 循环拼接数据到 document
                    var finalHtml = "";
                    for (var i = 0; i < result.data.length; i++) {
      
      
                        var item = result.data[i];
                        finalHtml += '<div class="blog">\n' +
                            '                <div class="title">' + item.title + '</div>\n' +
                            '                <div class="date">' + item.createTime + '</div>\n' +
                            '                <div class="desc">' + item.content + '</div>\n' +
                            '                <a href="blog_content.html?id=' + item.id + '" class="detail">查看详情</a>&nbsp;&nbsp;' +
                            '            </div>';
                    }
                    jQuery("#listDiv").html(finalHtml);
                }
            }
        });
    }

    getList();

    // 首页
    function firstClick() {
      
      
        location.href = "blog_list.html";
    }

    // 上一页
    function beforeClick() {
      
      
        if (PageIndex <= 1) {
      
      
            alert("已经是首页了!");
            return false;
        }
        PageIndex = parseInt(PageIndex) - 1;
        location.href = "blog_list.html?PageIndex=" + PageIndex + "&PageSize=" + PageSize;
    }

    // 下一页
    function nextClick() {
      
      
        if (PageIndex >= TotalPage) {
      
      
            alert("已经是末页了!");
            return false;
        }
        PageIndex = parseInt(PageIndex) + 1;
        location.href = "blog_list.html?PageIndex=" + PageIndex + "&PageSize=" + PageSize;
    }

    // 末页
    function lastClick() {
      
      
        PageIndex = TotalPage;
        location.href = "blog_list.html?PageIndex=" + PageIndex + "&PageSize=" + PageSize;
    }
</script>
</body>
</html>

② 后端代码(分页功能)

分页要素:

  1. 页码(PageIndex):要查询第几页的数据;
  2. 容量(PageSize):每页最多显示多少条数据。

分页语法(SQL):

-- 注意 (PageIndex-1)*PageSize 这种sql是不对的,后端需要自行计算,然后传给mapper
limit PageSize offset (PageIndex-1)*PageSize

Ⅰ. 数据库

ArticleMapper:->getList

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

    /**
     * 根据用户id(uid)查询所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(@Param("uid") Integer uid);

    /**
     * 分页——查询所有文章
     *
     * @param Offset
     * @param PageSize
     * @return
     */
    public List<ArticleInfo> getList(@Param("Offset") Integer Offset,
                                     @Param("PageSize") Integer PageSize);

    /**
     * 根据文章id(id)获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetail(@Param("aid") Integer aid);

    /**
     * 根据文章id(id)和用户id(uid)修改文章的标题和内容
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(@Param("aid") Integer aid,
                      @Param("uid") Integer uid,
                      @Param("title") String title,
                      @Param("content") String content);

    /**
     * 根据文章id(id)删除文章
     *
     * @param aid
     * @return
     */
    public int delete(@Param("aid") Integer aid);

    /**
     * 添加文章(uid,文章标题,文章内容)
     *
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int add(@Param("uid") Integer uid,
                   @Param("title") String title,
                   @Param("content") String content);
}

ArticleMapper.xml:->getList

<?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.mycnblog.mapper.ArticleMapper">
    <select id="getMyList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where uid = #{uid}
    </select>

    <select id="getList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo limit #{PageSize}
        offset #{Offset}
    </select>

    <select id="getDetail" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where id = #{aid}
    </select>

    <update id="update">
        update articleinfo
        set title=#{title},
            content=#{content}
        where id = #{aid}
          and uid = #{uid}
    </update>

    <delete id="delete">
        delete
        from articleinfo
        where id = #{aid}
    </delete>

    <insert id="add">
        insert into articleinfo(title, content, uid)
        values (#{title}, #{content}, #{uid});
    </insert>
</mapper>

Ⅱ. 服务层

ArticleService:->getList

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.ArticleMapper;
import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 用户博客列表,查询用户的所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(Integer uid) {
    
    
        return articleMapper.getMyList(uid);
    }

    /**
     * 分页——查询所有文章
     *
     * @param Offset
     * @param PageSize
     * @return
     */
    public List<ArticleInfo> getList(Integer Offset, Integer PageSize) {
    
    
        return articleMapper.getList(Offset, PageSize);
    }

    /**
     * 用户博客列表,获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetil(Integer aid) {
    
    
        return articleMapper.getDetail(aid);
    }

    /**
     * 修改页面,修改文章
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(Integer aid, Integer uid, String title, String content) {
    
    
        return articleMapper.update(aid, uid, title, content);
    }

    /**
     * 用户博客列表,删除文章
     *
     * @param aid
     * @return
     */
    public int delete(Integer aid) {
    
    
        return articleMapper.delete(aid);
    }

    /**
     * 博客编辑,发布文章
     *
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int add(Integer uid, String title, String content) {
    
    
        return articleMapper.add(uid, title, content);
    }
}

Ⅲ. 控制层

ArticleController:->getList

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.common.SessionUtil;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     *
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
    
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

	/**
     * 分页——查询所有文章
     *
     * @param PageIndex
     * @param PageSize
     * @return
     */
    @RequestMapping("/list")
    public List<ArticleInfo> getList(Integer PageIndex, Integer PageSize) {
    
    
        if (PageIndex == null || PageSize == null || PageIndex <= 0 || PageSize <= 0) {
    
    
            return null;
        }
        // 分页公式,计算偏移量
        int Offset = (PageIndex - 1) * PageSize;
        return articleService.getList(Offset, PageSize);
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public Object getDetil(Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            return AjaxResult.success(articleService.getDetil(aid));
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,查询文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/detailbyid")
    public Object getDetailById(HttpServletRequest request, Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            // 根据文章查询文章的详情
            ArticleInfo articleInfo = articleService.getDetil(aid);
            // 文章的归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if (userInfo != null && articleInfo != null &&
                    userInfo.getId() == articleInfo.getUid()) {
    
     // 文章归属人正确
                return AjaxResult.success(articleInfo);
            }
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,修改文章
     *
     * @param request
     * @param aid
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/update")
    public int update(HttpServletRequest request, Integer aid, String title, String content) {
    
    
        // 非空校验
        if (aid == null || !StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.update(aid, userInfo.getId(), title, content);
        }
        return 0;
    }

    /**
     * 博客列表,删除文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/delete")
    public int delete(HttpServletRequest request, Integer aid) {
    
    
        // 非空校验
        if (aid == null && aid <= 0) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
     // 当前已登录
            return articleService.delete(aid);
        }
        return 0;
    }

    /**
     * 博客编辑,发布文章
     *
     * @param request
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/add")
    public int add(HttpServletRequest request, String title, String content) {
    
    
        // 非空校验
        if (!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.add(userInfo.getId(), title, content);
        }
        return 0;
    }
}

Ⅳ. 拦截器

这里放行,是因为这个博客列表是所有文章的列表,用户不需要登录就可以访问。

AppConfig:->放行文章分页列表

package com.example.mycnblog.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

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

/**
 * 配置拦截器
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {
    
    

    // 不拦截的 url 集合
    List<String> excludes = new ArrayList<String>(){
    
    {
    
    
        add("/**/*.html");
        add("/js/**"); // 放行 static/js 下的所有文件
        add("/editor.md/**"); // 放行 static/editor.md 下的所有文件
        add("/css/**"); // 放行 static/css 下的所有文件
        add("/img/**"); // 放行 static/img 下的所有文件
        add("/user/reg"); // 放行注册接口
        add("/user/login"); // 放行登录接口
        add("/art/detail"); // 放行文章详情接口
        add("/art/list"); // 放行文章分页列表
    }};

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        // 配置拦截器
        InterceptorRegistration registration =
                registry.addInterceptor(loginInterceptor);
        registration.addPathPatterns("/**"); // 拦截所有请求
        registration.excludePathPatterns(excludes); // 排除不拦截的 url
    }
}

Ⅴ. 效果演示

在这里插入图片描述

③ 后端代码(尾页功能)

尾页:这里计算尾页的方法,主要是文章总数 / 每页容量,但是要向上进位。
例如——5 / 2 = 2.5 -> 3,这里的进位可以采用 Math 类里的 ceil 方法;但是又有一个问题,对于 int 类型 5 / 2 = 2,所以要再 * 1.0,这样就是 2.5 了。

Ⅰ. 数据库

ArticleMapper:->getTotalCount

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 文章表的 mapper
 */
@Mapper
public interface ArticleMapper {
    
    

    /**
     * 根据用户id(uid)查询所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(@Param("uid") Integer uid);

    /**
     * 分页——查询所有文章
     *
     * @param Offset
     * @param PageSize
     * @return
     */
    public List<ArticleInfo> getList(@Param("Offset") Integer Offset,
                                     @Param("PageSize") Integer PageSize);

    /**
     * 跳转文章列表尾页——查询文章总数
     *
     * @return
     */
    public int getTotalCount();

    /**
     * 根据文章id(id)获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetail(@Param("aid") Integer aid);

    /**
     * 根据文章id(id)和用户id(uid)修改文章的标题和内容
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(@Param("aid") Integer aid,
                      @Param("uid") Integer uid,
                      @Param("title") String title,
                      @Param("content") String content);

    /**
     * 根据文章id(id)删除文章
     *
     * @param aid
     * @return
     */
    public int delete(@Param("aid") Integer aid);

    /**
     * 添加文章(uid,文章标题,文章内容)
     *
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int add(@Param("uid") Integer uid,
                   @Param("title") String title,
                   @Param("content") String content);
}

ArticleMapper.xml:->getTotalCount

<?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.mycnblog.mapper.ArticleMapper">
    <select id="getMyList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where uid = #{uid}
    </select>

    <select id="getList" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo limit #{PageSize}
        offset #{Offset}
    </select>

    <select id="getTotalCount" resultType="java.lang.Integer">
        select count(*)
        from articleinfo
    </select>

    <select id="getDetail" resultType="com.example.mycnblog.model.ArticleInfo">
        select *
        from articleinfo
        where id = #{aid}
    </select>

    <update id="update">
        update articleinfo
        set title=#{title},
            content=#{content}
        where id = #{aid}
          and uid = #{uid}
    </update>

    <delete id="delete">
        delete
        from articleinfo
        where id = #{aid}
    </delete>

    <insert id="add">
        insert into articleinfo(title, content, uid)
        values (#{title}, #{content}, #{uid});
    </insert>
</mapper>

Ⅱ. 服务层

ArticleService:

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.ArticleMapper;
import com.example.mycnblog.model.ArticleInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * 文章表的服务层
 */
@Service
public class ArticleService {
    
    

    @Autowired
    private ArticleMapper articleMapper;

    /**
     * 用户博客列表,查询用户的所有文章
     *
     * @param uid
     * @return
     */
    public List<ArticleInfo> getMyList(Integer uid) {
    
    
        return articleMapper.getMyList(uid);
    }

    /**
     * 分页——查询所有文章
     *
     * @param Offset
     * @param PageSize
     * @return
     */
    public List<ArticleInfo> getList(Integer Offset, Integer PageSize) {
    
    
        return articleMapper.getList(Offset, PageSize);
    }

    /**
     * 跳转文章列表尾页——查询文章总数
     *
     * @return
     */
    public int getTotalCount() {
    
    
        return articleMapper.getTotalCount();
    }

    /**
     * 用户博客列表,获取文章的详情信息
     *
     * @param aid
     * @return
     */
    public ArticleInfo getDetil(Integer aid) {
    
    
        return articleMapper.getDetail(aid);
    }

    /**
     * 修改页面,修改文章
     *
     * @param aid
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int update(Integer aid, Integer uid, String title, String content) {
    
    
        return articleMapper.update(aid, uid, title, content);
    }

    /**
     * 用户博客列表,删除文章
     *
     * @param aid
     * @return
     */
    public int delete(Integer aid) {
    
    
        return articleMapper.delete(aid);
    }

    /**
     * 博客编辑,发布文章
     *
     * @param uid
     * @param title
     * @param content
     * @return
     */
    public int add(Integer uid, String title, String content) {
    
    
        return articleMapper.add(uid, title, content);
    }
}

Ⅲ. 控制层

ArticleController:

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.common.SessionUtil;
import com.example.mycnblog.model.ArticleInfo;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * 文章控制器
 */
@RestController
@RequestMapping("/art")
public class ArticleController {
    
    

    @Autowired
    private ArticleService articleService;

    /**
     * 查询用户的所有文章
     *
     * @param request
     * @return
     */
    @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
    
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

    /**
     * 分页——查询所有文章
     *
     * @param PageIndex
     * @param PageSize
     * @return
     */
    @RequestMapping("/list")
    public List<ArticleInfo> getList(Integer PageIndex, Integer PageSize) {
    
    
        if (PageIndex == null || PageSize == null || PageIndex <= 0 || PageSize <= 0) {
    
    
            return null;
        }
        // 分页公式,计算偏移量
        int Offset = (PageIndex - 1) * PageSize;
        return articleService.getList(Offset, PageSize);
    }

    /**
     * 跳转文章列表尾页——获取尾页
     *
     * @param PageSize
     * @return
     */
    @RequestMapping("/totalpage")
    public Integer getTotalPage(Integer PageSize) {
    
    
        if (PageSize != null && PageSize > 0) {
    
    
            // 参数有效
            // 文章总数
            int TotalCount = articleService.getTotalCount();
            // 总页数
            int TotalPage = (int) Math.ceil(TotalCount * 1.0 / PageSize);
            return TotalPage;
        }
        return null;
    }

    /**
     * 获取文章的详情信息
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public Object getDetil(Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            return AjaxResult.success(articleService.getDetil(aid));
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,查询文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/detailbyid")
    public Object getDetailById(HttpServletRequest request, Integer aid) {
    
    
        if (aid != null && aid > 0) {
    
    
            // 根据文章查询文章的详情
            ArticleInfo articleInfo = articleService.getDetil(aid);
            // 文章的归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if (userInfo != null && articleInfo != null &&
                    userInfo.getId() == articleInfo.getUid()) {
    
     // 文章归属人正确
                return AjaxResult.success(articleInfo);
            }
        }
        return AjaxResult.fail(-1, "查询失败");
    }

    /**
     * 修改页面,修改文章
     *
     * @param request
     * @param aid
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/update")
    public int update(HttpServletRequest request, Integer aid, String title, String content) {
    
    
        // 非空校验
        if (aid == null || !StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.update(aid, userInfo.getId(), title, content);
        }
        return 0;
    }

    /**
     * 博客列表,删除文章
     *
     * @param request
     * @param aid
     * @return
     */
    @RequestMapping("/delete")
    public int delete(HttpServletRequest request, Integer aid) {
    
    
        // 非空校验
        if (aid == null && aid <= 0) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null) {
    
     // 当前已登录
            return articleService.delete(aid);
        }
        return 0;
    }

    /**
     * 博客编辑,发布文章
     *
     * @param request
     * @param title
     * @param content
     * @return
     */
    @RequestMapping("/add")
    public int add(HttpServletRequest request, String title, String content) {
    
    
        // 非空校验
        if (!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            // 参数有误
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if (userInfo != null && userInfo.getId() > 0) {
    
    
            return articleService.add(userInfo.getId(), title, content);
        }
        return 0;
    }
}

Ⅳ. 拦截器

同理,这里也需要放行。

AppConfig:->放行文章分页总页数接口

package com.example.mycnblog.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

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

/**
 * 配置拦截器
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {
    
    

    // 不拦截的 url 集合
    List<String> excludes = new ArrayList<String>(){
    
    {
    
    
        add("/**/*.html");
        add("/js/**"); // 放行 static/js 下的所有文件
        add("/editor.md/**"); // 放行 static/editor.md 下的所有文件
        add("/css/**"); // 放行 static/css 下的所有文件
        add("/img/**"); // 放行 static/img 下的所有文件
        add("/user/reg"); // 放行注册接口
        add("/user/login"); // 放行登录接口
        add("/art/detail"); // 放行文章详情接口
        add("/art/list"); // 放行文章分页列表接口
        add("/art/totalpage"); // 放行文章分页总页数接口 
    }};

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        // 配置拦截器
        InterceptorRegistration registration =
                registry.addInterceptor(loginInterceptor);
        registration.addPathPatterns("/**"); // 拦截所有请求
        registration.excludePathPatterns(excludes); // 排除不拦截的 url
    }
}

Ⅴ. 效果演示

进入文章分页列表:
在这里插入图片描述
点击尾页:
在这里插入图片描述

9、密码加盐

很多人其实在这里会用到 md5,直接加盐,但是实际上这种方式并不好,虽然 md5 加密是不可逆的,但是由于 md5 加密的相同密码对应的加密后密码相同,并且都是32位的,这样我们就可以做一个彩虹表,来破解了。

理想的加盐方式,其实就是我们每一次都随机盐值,这样加密出来的密码,想要破解就会浪费大量的时间,安全性也更高了。

既然加密了,那肯定就要有验证密码,我们验证密码需要三个数据,一个是待验证的密码,一个是盐值,一个是已经加密后的密码。所以我们其实可以将验证和加密后的密码拼接起来,这样我们的密码长度也很长,然后我们可以再用 md5 进行加密,因为 md5 加密后,密文都是32位的,这样我们就可以根据位数进行分割盐值和密码。

通过上述分析,公式为:
最终密码 = (md5)(盐值 + 原始密码)

① 工具类

common->SecurityUtil:

package com.example.mycnblog.common;

import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.util.UUID;

/**
 * 加盐加密类
 */
public class SecurityUtil {
    
    

    /**
     * 加盐加密
     *
     * @param password
     * @return
     */
    public static String encrypt(String password) {
    
    
        // 每次生成内容不同的,但长度固定的 32 位盐值
        String salt = UUID.randomUUID().toString().replace("-", "");
        // 最终密码 = (md5)(盐值 + 原始密码)
        String finalPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        return salt + finalPassword;
    }

    /**
     * 密码验证
     *
     * @param password      待验证密码
     * @param finalPassword 数据库中加盐的密码
     * @return
     */
    public static boolean decrypt(String password, String finalPassword) {
    
    
        // 非空效验
        if (!StringUtils.hasLength(password) || !StringUtils.hasLength(finalPassword)) {
    
    
            return false;
        }
        // 最终密码不正确
        if (finalPassword.length() != 64) {
    
    
            return false;
        }
        // 获取盐值
        String salt = finalPassword.substring(0, 32);
        // 加盐密码 = (md5)(盐值 + 待确定密码)
        String securityPassword =
                DigestUtils.md5DigestAsHex((salt + password).getBytes());
        // (盐值 + 加盐密码) 与 (最终密码) 进行比较
        return (salt + securityPassword).equals(finalPassword);
    }
}

② 具体实现

这个地方主要是要修改注册和登录这两个接口。

Ⅰ. 数据库

UserMapper:->getUserByName

package com.example.mycnblog.mapper;

import com.example.mycnblog.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 用户表的 mapper
 */
@Mapper
public interface UserMapper {
    
    

    /**
     * 用户注册
     *
     * @param username
     * @param password
     * @return
     */
    public int add(@Param("username") String username,
                   @Param("password") String password);

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return
     */
    public UserInfo login(@Param("username") String username,
                          @Param("password") String password);

    /**
     * 根据用户名获取用户信息
     *
     * @param username
     * @return
     */
    public UserInfo getUserByName(@Param("username") String username);

    /**
     * 通过用户 id 查询用户信息
     *
     * @param uid
     * @return
     */
    public UserInfo myInfoByUid(@Param("uid") Integer uid);
}

UserMapper.xml:->getUserByName

<?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.mycnblog.mapper.UserMapper">
    <insert id="add">
        insert into userinfo(username, password)
        values (#{username}, #{password})
    </insert>

    <select id="login" resultType="com.example.mycnblog.model.UserInfo">
        select *
        from userinfo
        where username = #{username}
          and password = #{password}
    </select>

    <select id="getUserByName" resultType="com.example.mycnblog.model.UserInfo">
        select *
        from userinfo
        where username = #{username}
    </select>

    <select id="myInfoByUid" resultType="com.example.mycnblog.model.UserInfo">
        select *
        from userinfo
        where id = #{uid}
    </select>
</mapper>

Ⅱ. 服务层

UserService:->getUserByName

package com.example.mycnblog.service;

import com.example.mycnblog.mapper.UserMapper;
import com.example.mycnblog.model.UserInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户表的服务层
 */
@Service
public class UserService {
    
    

    @Autowired
    private UserMapper userMapper;

    /**
     * 用户注册
     *
     * @param username
     * @param password
     * @return
     */
    public int add(String username, String password) {
    
    
        return userMapper.add(username, password);
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return
     */
    public UserInfo login(String username, String password) {
    
    
        return userMapper.login(username, password);
    }

    /**
     * 用户登录——根据用户名获取用户信息
     *
     * @param username
     * @return
     */
    public UserInfo getUserByName(String username) {
    
    
        return userMapper.getUserByName(username);
    }

    /**
     * 获取作者信息
     *
     * @param uid
     * @return
     */
    public UserInfo myInfoByUid(Integer uid) {
    
    
        return userMapper.myInfoByUid(uid);
    }
}

Ⅲ. 控制层

UserController:->reg / login

package com.example.mycnblog.controller;

import com.example.mycnblog.common.AjaxResult;
import com.example.mycnblog.common.Constant;
import com.example.mycnblog.common.SecurityUtil;
import com.example.mycnblog.model.UserInfo;
import com.example.mycnblog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 用户控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {
    
    

    @Autowired
    private UserService userService;

    /**
     * 用户注册
     *
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/reg")
    public Object reg(String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1, "非法的参数请求!");
        }
        // 2.进行添加操作
        int result = userService.add(username, SecurityUtil.encrypt(password));
        if (result == 1) {
    
    
            return AjaxResult.success("注册成功!", 1);
        } else {
    
    
            return AjaxResult.fail(-1, "数据库添加出错!");
        }
    }

    /**
     * 用户登录
     *
     * @param username
     * @param password
     * @return 如果用户名和密码都正确,返回1;如果用户名或密码为空/不正确,返回非1
     */
    @RequestMapping("/login")
    public int login(HttpServletRequest request, String username, String password) {
    
    
        // 1.非空效验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            // 参数有误
            return 0;
        }
        // 2.进行查找操作
        UserInfo userInfo = userService.getUserByName(username);
        if (userInfo == null || userInfo.getId() <= 0) {
    
     // userinfo 无效
            // 用户名错误
            return -1;
        } else {
    
    
            // 用户名正确
            boolean result = SecurityUtil.decrypt(password, userInfo.getPassword());
            if (result) {
    
    
                // 密码正确
                // 将 userinfo 保存到 session 中
                HttpSession session = request.getSession();
                session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
                return 1;
            }
            // 密码错误
            return -1;
        }
    }

    /**
     * 退出登录
     *
     * @param request
     * @return
     */
    @RequestMapping("/logout")
    public boolean logout(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            // 移除 session 中当前登录的用户
            session.removeAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return true;
    }

    /**
     * 获取个人信息
     *
     * @param request
     * @return
     */
    @RequestMapping("/myinfo")
    public UserInfo myInfo(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
    
    
            return (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return null;
    }

    /**
     * 获取作者信息
     *
     * @param uid
     * @return
     */
    @RequestMapping("/myinfobyuid")
    public UserInfo myInfoByUid(Integer uid) {
    
    
        if (uid != null && uid > 0) {
    
    
            return userService.myInfoByUid(uid);
        }
        return null;
    }
}

③ 效果演示

来到注册页面:李四 / 123456
在这里插入图片描述
来到登录页面:李四 / 123456
在这里插入图片描述

总结

这个项目还是花了很多功夫的,完成之后也是松了一口气,复习了很多知识,也从中学到了一些新知识,文章中实现的功能都是一些基础部分,还有一些进阶部分,各位可以自行实现!

可扩展功能:

  1. 文章保存草稿
  2. 文章定时发布功能
  3. 用户多次尝试登录,账号冻结
  4. 评论功能
  5. 个人中心
  6. 登录界面的验证码(增加系统的安全性)
  7. 找回密码
  8. 文章点赞/踩

猜你喜欢

转载自blog.csdn.net/WZRbeliever/article/details/128571428