个人博客系统项目大全【6万字】

一、数据库设计

1. 设计表(用户7,文章9,评论5)

sql 复制代码
-- 创建⽤户表
create table userinfo(
	uid bigint auto_increment primary key comment '主键',
	loginname varchar(50) not null unique comment '登录⽤户	名',
	nickname varchar(50) default '' comment '昵称',
	`password` varchar(65) not null comment '密码',
	github varchar(255) comment 'github地址',
	photo varchar(255) comment '头像',
	`state` tinyint not null DEFAULT 1 comment '⽤户状态,1=正常|-1=异常|-2=永久冻结|-3=临时冻结'
);
insert into userinfo(loginname,`password`) values('admin','admin');

create table articleinfo(
	aid BIGINT auto_increment primary key comment '主键',
	`title` varchar(255) not null comment '标题',
	createtime TIMESTAMP not null default CURRENT_TIMESTAMP() comment '创建时间',
	updatetime TIMESTAMP not null DEFAULT CURRENT_TIMESTAMP() comment '修改时间',
	`desc` varchar(255) not null comment '⽂章简介',
	`content` longtext not null comment '⽂章正⽂',
	`state` TINYINT DEFAULT -1 comment '状态:-1=草稿|1=已发布',
	uid bigint not null comment '作者id',
	`rcount` bigint default 1 comment '阅读量'
);

-- 创建评论表
create table commentinfo(
	cid bigint auto_increment primary key comment '评论表的主键',
	aid bigint not null comment '⽂章表id',
	uid bigint not null comment '⽤户id',
	`content` varchar(500) not null comment '评论正⽂',
	createtime TIMESTAMP default CURRENT_TIMESTAMP() comment '评论的发表时间'
);

2. 创建model(java类)

时间戳 LocalDateTime 和 TIMESTAMP
类为什么要序列化

什么是序列化?在什么情况下将类序列化?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

序列化:序列化是将对象转换为容易传输的格式的过程。例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象。在另一端,反序列化将从该流重新构造对象是对象永久化的一种机制。

确切的说应该是对象的序列化,一般程序在运行时,产生对象,这些对象随着程序的停止运行而消失,但如果我们想把某些对象(因为是对象,所以有各自不同的特性)保存下来,在程序终止运行后,这些对象仍然存在,可以在程序再次运行时读取这些对象的值,或者在其他程序中利用这些保存下来的对象。这种情况下就要用到对象的序列化。

对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整性和可传递性。譬如通过网络传输,或者把一个对象保存成一个文件的时候,要实现序列化接口 。

java 复制代码
package com.example.myblog.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 文章的实体类
 */
@Setter
@Getter
@TableName("articleinfo")//数据库名字和类名不一致时 使用
public class ArticleInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = -52722551515977433L;
    @TableId(type = IdType.AUTO)
    private Long aid;
    private String title;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    @TableField("`desc`")
    private String desc;
    private String content;
    private int state;
    private long uid;
    private long rcount;
}
java 复制代码
package com.example.myblog.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 评论实体类
 */
@Setter
@Getter
@TableName("commentinfo")
public class CommentInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = -5849198667119918959L;
    @TableId(type= IdType.AUTO)
    private long cid;
    private long aid;
    private long uid;
    private String content;
    private LocalDateTime createtime;
}
java 复制代码
package com.example.myblog.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.myblog.model.vo.UserInfoVO;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;

/**
 * 实体类
 */
@Setter
@Getter
@TableName("userinfo")
public class UserInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = 6259842500624893439L;
    @TableId(type= IdType.AUTO)
    private long uid;
    private String loginname;
    private String nickname;
    private String password;
    private String github;
    private String photo;
    private int state;

}

用户模块(注册、登录、退出)

  1. 注册功能:1.效验参数的正确性 2.密码加盐 3.验证验证码是否正确 4.调用服务执行添加操作(添加到数据库) 5.将执行结果返回给前端
  2. 登录功能:1.非空效验 2.验证验证码 3.清除验证码 4.根据登录名查询到用户对象 5.判断用户对象是否为空,说明当前登录名不存在 6.如果用户对象不为空,使用用户输入的密码和用户对象的真实密码 7.如果正确 加入到session
  3. 退出功能:1.在session中去除
  4. 获取

二、注册功能

java 复制代码
/**
 * 用户控制器 -> 所有前端请求会先经过此控制
 */
@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private IUserService userService;
    @Autowired
    private IArticleInfoService articleInfoService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Value("${imagepath}")
    private String imagePath; // 图片的保存地址

    @RequestMapping("/reg")
    public AjaxResult reg(UserInfoVO userInfoVO) {
        // 1.效验参数的正确性
        // 1.1 非空效验
        if (userInfoVO == null || !StringUtils.hasLength(userInfoVO.getLoginname()) ||
                !StringUtils.hasLength(userInfoVO.getPassword()) ||
                !StringUtils.hasLength(userInfoVO.getCheckCode())) {
            // 非法参数
            return AjaxResult.fail(-1, "非法参数");
        }
        // 密码加盐
        userInfoVO.setPassword(PasswordUtil.encrypt(userInfoVO.getPassword()));
        // todo:1.2 验证验证码是否正确
        // 2.调用服务执行添加操作(添加到数据库)
        int result = userService.reg(userInfoVO);
        // 3.将执行结果返回给前端
        return AjaxResult.succ(result);
    }

    @RequestMapping("/login")
    public AjaxResult login(UserInfoVO userInfoVO, HttpSession session) {
        // 1.非空效验
        if (userInfoVO == null || !StringUtils.hasLength(userInfoVO.getLoginname()) ||
                !StringUtils.hasLength(userInfoVO.getCheckCode()) ||
                !StringUtils.hasLength(userInfoVO.getCodeKey())) {
            // 参数有误
            return AjaxResult.fail(-1, "非法参数");
        }
        // redis 里面 key 对应的真是的验证码
        String redisCodeValue = (String) redisTemplate.opsForValue().get(userInfoVO.getCodeKey());
        if (!StringUtils.hasLength(redisCodeValue) || !redisCodeValue.equals(userInfoVO.getCheckCode())) {
            // 验证码不正确
            return AjaxResult.fail(-1, "验证码错误");
        }
        // 清除 redis 中的验证码
        redisTemplate.opsForValue().set(userInfoVO.getCodeKey(), "");
        // 2.根据登录名查询到用户对象
        UserInfo userInfo = userService.getUserByLoginName(userInfoVO.getLoginname());
        // 3.判断用户对象是否为空,说明当前登录名不存在
        if (userInfo == null || userInfo.getUid() <= 0) {
            // 用户对象为null或者用户为无效对象
            return AjaxResult.fail(-1, "用户名或密码错误!");
        }
        // 4.如果用户对象不为空,使用用户输入的密码和用户对象的真实密码
        // 进行比较
        if (PasswordUtil.decrypt(userInfoVO.getPassword(), userInfo.getPassword())) {
            // 5.如果输入密码和真实的密码相同,将用户对象存储到 Session
            session.setAttribute(AppVar.SESSION_KEY_USERINFO, userInfo);
            userInfo.setPassword(""); // 传递对象之前,先将敏感信息密码去除
            return AjaxResult.succ(userInfo);
        } else {
            // 6.如果输入的密码和真实密码不相同,说明密码错误
            return AjaxResult.fail(-1, "用户名或密码错误!");
        }
    }

    @RequestMapping("/logout")
    public AjaxResult logout(HttpSession session) {
        try {
            // 清楚当前登录用户的 session 信息
            session.removeAttribute(AppVar.SESSION_KEY_USERINFO);
        } catch (Exception e) {
            return AjaxResult.fail(-1, "清楚失败");
        }
        return AjaxResult.succ(1);
    }

    @RequestMapping("/getuser")
    public AjaxResult getUser() {
        List<UserInfo> list = userService.list();
        System.out.println(list);
        return AjaxResult.succ(list);
    }

    @RequestMapping("/getsess")
    public AjaxResult getSess(HttpServletRequest request) {
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            return AjaxResult.fail(-2, "当前用户未登录!");
        }
        return AjaxResult.succ(userInfo);
    }

    /**
     * 查询当前文章是否属于当前登录用户
     */
    @RequestMapping("/isartbyme")
    public AjaxResult isArtByMe(Integer aid, HttpServletRequest request) {
        if (aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "参数有误!");
        }
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            return AjaxResult.fail(-2, "当前用户未登录!");
        }
        ArticleInfo articleInfo = articleInfoService.getById(aid);
        if (articleInfo != null && articleInfo.getAid() >= 0
                && articleInfo.getUid() == userInfo.getUid()) {
            // 文章是当前登录用户的
            return AjaxResult.succ(1);
        }
        return AjaxResult.succ(0);
    }

    @RequestMapping("/save_photo")
    public AjaxResult savePhoto(MultipartFile file, HttpServletRequest request) {
        // 1.保存图片到服务器
        // 得到图片的后缀
        String imageType = file.getOriginalFilename().
                substring(file.getOriginalFilename().lastIndexOf("."));
        // 生成图片的名称
        String imgName = UUID.randomUUID().toString()
                .replace("-", "") + imageType;
        try {
            file.transferTo(new File(imagePath + imgName));
        } catch (IOException e) {
            return AjaxResult.fail(-1, "图片上传失败!");
        }
        String imgUrl = "/image/" + imgName;
        // 2.将图片地址保存到数据库
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            // 未登录
            return AjaxResult.fail(-2, "请先登录!");
        }
        UpdateWrapper<UserInfo> wrapper = new UpdateWrapper<>();
        wrapper.set(true, "photo", imgUrl);
        wrapper.eq("uid", userInfo.getUid());
        boolean result = userService.update(wrapper);
        if (result) {
            // 图片保存成功
            // 更新头像到 session 中
            userInfo.setPhoto(imgUrl);
            HttpSession session = request.getSession();
            session.setAttribute(AppVar.SESSION_KEY_USERINFO, userInfo);
            return AjaxResult.succ(imgUrl);
        } else {
            return AjaxResult.fail(-3, "数据库修改失败");
        }
    }

    /**
     * 个人中心修改昵称或密码
     *
     * @param nickname
     * @param oldpassword
     * @param password
     * @param isUpdatePassword
     * @return
     */
    @RequestMapping("/update")
    public AjaxResult update(String nickname, String oldpassword,
                             String password, Boolean isUpdatePassword,
                             HttpServletRequest request) {
        // 1.参数效验
        if (!StringUtils.hasLength(nickname)) {
            return AjaxResult.fail(-1, "非法参数!");
        }
        // 是否要修改密码
        if (isUpdatePassword) {
            // 修改原密码
            if (!StringUtils.hasLength(oldpassword) || !StringUtils.hasLength(password)) {
                return AjaxResult.fail(-1, "非法参数!");
            }
        }
        // 2.组装数据(从 Session 获取用户信息)
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            return AjaxResult.fail(-2, "请先登录!");
        }
        userInfo.setNickname(nickname);
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        // 判断是否修改密码,如果修改密码,则需要验证用户输入的原密码是否正确
        if (isUpdatePassword) {
            UserInfo dbUser = userService.getById(userInfo.getUid());
            // 需要修改密码,验证原密码是否正确
            boolean checkPassword = PasswordUtil.decrypt(oldpassword, dbUser.getPassword());
            if (!checkPassword) {
                return AjaxResult.fail(-3, "原密码输入错误!");
            }
            // 修改密码
            password = PasswordUtil.encrypt(password); // 加盐之后的密码
            updateWrapper.set("password", password);
        }
        // 3.修改数据库
        updateWrapper.eq("uid", userInfo.getUid());
        updateWrapper.set("nickname", nickname);
        boolean result = userService.update(updateWrapper);
        // 4.将结果返回给前端
        return AjaxResult.succ(result ? 1 : 0);
    }

}

1. 扩展传递对象 UserInfoVO

java 复制代码
package com.example.myblog.model.vo;


import com.example.myblog.model.UserInfo;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;

@Setter
@Getter
public class UserInfoVO extends UserInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = 1660890311645535668L;
    private String checkCode;
    private String codeKey;
}

2. 后端注册功能实现 controller

java 复制代码
    @RequestMapping("/reg")
    public AjaxResult reg(UserInfoVO userInfoVO) {
        // 1.效验参数的正确性
        // 1.1 非空效验
        if (userInfoVO == null || !StringUtils.hasLength(userInfoVO.getLoginname()) ||
                !StringUtils.hasLength(userInfoVO.getPassword()) ||
                !StringUtils.hasLength(userInfoVO.getCheckCode())) {
            // 非法参数
            return AjaxResult.fail(-1, "非法参数");
        }
        String redisCodeValue = (String) redisTemplate.opsForValue().get(userInfoVO.getCodeKey());
        if (!StringUtils.hasLength(redisCodeValue) || !redisCodeValue.equals(userInfoVO.getCheckCode())) {
            // 验证码不正确
            return AjaxResult.fail(-1, "验证码错误");
        }
        // 清除 redis 中的验证码
        redisTemplate.opsForValue().set(userInfoVO.getCodeKey(), "");
        // 密码加盐
        userInfoVO.setPassword(PasswordUtil.encrypt(userInfoVO.getPassword()));
        // 2.调用服务执行添加操作(添加到数据库)
        int result = userService.reg(userInfoVO);
        // 3.将执行结果返回给前端
        return AjaxResult.succ(result);
    }

3. service实现调用数据库操作接口

java 复制代码
package com.example.myblog.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.myblog.dao.UserMapper;
import com.example.myblog.model.UserInfo;
import com.example.myblog.service.IUserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,UserInfo> implements IUserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public int reg(UserInfo userInfo) {
        return userMapper.reg(userInfo);
    }

    @Override
    public UserInfo getUserByLoginName(String loginname) {
        return userMapper.getUserByLoginName(loginname);
    }
}
java 复制代码
package com.example.myblog.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.myblog.model.UserInfo;

public interface IUserService extends IService<UserInfo> {
    int reg(UserInfo userInfo);
    UserInfo getUserByLoginName(String loginname);
}

4. dao 中 UserMapper

java 复制代码
package com.example.myblog.dao;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.myblog.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

public interface UserMapper extends BaseMapper<UserInfo> {

    // 添加用户
    int reg(UserInfo userInfo);

    // 根据登录名查询用户对象
    UserInfo getUserByLoginName(@Param("loginname")String name);

}

5. 实现 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.myblog.dao.UserMapper">
    <insert id="reg">
        insert into userinfo(loginname,password)
        values(#{loginname},#{password})
    </insert>

    <select id="getUserByLoginName" resultType="com.example.myblog.model.UserInfo">
        select * from userinfo
        where loginname=#{loginname}
    </select>

</mapper>

6. 前端实现

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">
    <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="login.html">登录</a>
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="login-container">
    <!-- 中间的注册框 -->
    <div class="login-dialog" style="height: 460px;">
        <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" style="margin-bottom: 20px;">
            <span>验证码</span>
            <input id="checkcode" style="width: 66px;">&nbsp;&nbsp;
            <img onclick="refreshCode()" id="codeimg" src="img/check_code.png"
                 style="height: 50px;width: 128px;">
        </div>
        <div class="row">
            <button id="submit" onclick="doReg()">提交</button>
        </div>
    </div>
</div>
<script>
    // 验证码key
    var codeKey = "";

    // 获取并显示验证码
    function loadCode() {
        jQuery.ajax({
            url: "/getcaptcha",
            type: "GET",
            data: {},
            success: function (res) {
                if (res.code = 200 && res.data != null && res.data != "") {
                    // 获取验证码成功
                    codeKey = res.data.codekey;
                    jQuery("#codeimg").attr("src", res.data.codeurl);
                }
            }
        });
    }

    loadCode();
    // 请求后端实现注册功能
    function doReg() {
        // 1.进行非空效验
        var username = jQuery("#username"); // 等于原生 document.getElementById("username")
        var password = jQuery("#password");
        var password2 = jQuery("#password2");
        var checkcode = jQuery("#checkcode");
        if (username.val().trim() == "") {
            alert("请先输入用户名!");
            username.focus();
            return false;
        }
        if (password.val().trim() == "") {
            alert("请先输入密码!");
            password.focus();
            return false;
        }
        if (password2.val().trim() == "") {
            alert("请先输入确认密码!");
            password2.focus();
            return false;
        }
        if (checkcode.val().trim() == "") {
            alert("请先输入验证码!");
            checkcode.focus();
            return false;
        }
        // 2.效验密码和确认密码要一致
        if (password.val() != password2.val()) {
            alert("两次输入的密码不一致,请先检查!");
            password.focus();
            return false;
        }
        // 3.发送 ajax 请求
        jQuery.ajax({
            url: "/user/reg",
            type: "POST",
            data: {
                "codeKey": codeKey,
                "loginname": username.val(),
                "password": password.val(),
                "checkCode": checkcode.val()
            }, success: function (res) {
                // 接受返回结果
                // 4.根据返回的结果,将结果呈现给用户
                if (res.code == 200 && res.data == 1) {
                    // 添加成功
                    alert("恭喜:注册成功!");
                    location.href = "login.html"; // 跳转到登录页面
                } else {
                    alert("抱歉:操作失败!" + res.msg);
                }
            }
        });
    }
</script>
</body>

</html>

7. 测试

java 复制代码
package com.example.myblog.controller;

import com.example.myblog.dao.ArticleInfoMapper;
import com.example.myblog.model.vo.UserInfoVO;
import com.example.myblog.util.AjaxResult;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class UserControllerTest {

    @Autowired
    private UserController userController;

    @Resource
    private ArticleInfoMapper articleInfoMapper;

    @Test
    void login() {
        UserInfoVO userInfoVO = new UserInfoVO();
        AjaxResult ajaxResult = userController.login(userInfoVO, null);
        System.out.println(ajaxResult.toString());
    }

    @Test
    void getUser() {
    }
}

三、登录功能

1. 前端和注册差不多

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">
    <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="reg.html">注册</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" style="margin-bottom: 20px;">
            <span>验证码</span>
            <input id="checkCode" style="width: 66px;">&nbsp;&nbsp;
            <img onclick="loadCode()" id="codeimg" src=""
                 style="height: 50px;width: 128px;">
        </div>
        <div class="row">
            <button id="submit" onclick="mySub()">登录</button>
        </div>
    </div>
</div>
<script>
    // 验证码key
    var codeKey = "";

    // 获取并显示验证码
    function loadCode() {
        jQuery.ajax({
            url: "/getcaptcha",
            type: "GET",
            data: {},
            success: function (res) {
                if (res.code = 200 && res.data != null && res.data != "") {
                    // 获取验证码成功
                    codeKey = res.data.codekey;
                    jQuery("#codeimg").attr("src", res.data.codeurl);
                }
            }
        });
    }

    loadCode();

    function mySub() {
        // 1.非空效验
        var loginname = jQuery("#username");
        var password = jQuery("#password");
        var checkCode = jQuery("#checkCode");
        if (loginname.val().trim() == "") {
            alert("请先输入用户名!");
            loginname.focus();
            return false;
        }
        if (password.val().trim() == "") {
            alert("请先输入密码!");
            password.focus();
            return false;
        }
        if (checkCode.val().trim() == "") {
            alert("请先输入验证码!");
            checkCode.focus();
            return false;
        }
        // 2.发送 ajax 请求到后端
        jQuery.ajax({
            url: "/user/login",
            type: "GET",
            data: {
                "codeKey": codeKey,
                "loginname": loginname.val(),
                "password": password.val(),
                "checkCode": checkCode.val()
            }, success: function (res) {
                // 3.根据后端返回的结果,展示用户
                if (res.code == 200 && res.data != null && res.data.uid > 0) {
                    // 登录成功
                    location.href = "myblog_list.html"; // 跳转到我的文章管理页
                } else {
                    alert("抱歉:登录失败!" + res.msg);
                }
            }
        });
    }
</script>
</body>

</html>

2. controller

java 复制代码
    @RequestMapping("/login")
    public AjaxResult login(UserInfoVO userInfoVO, HttpSession session) {
        // 1.非空效验
        if (userInfoVO == null || !StringUtils.hasLength(userInfoVO.getLoginname()) ||
                !StringUtils.hasLength(userInfoVO.getCheckCode()) ||
                !StringUtils.hasLength(userInfoVO.getCodeKey())) {
            // 参数有误
            return AjaxResult.fail(-1, "非法参数");
        }
        // redis 里面 key 对应的真是的验证码
        String redisCodeValue = (String) redisTemplate.opsForValue().get(userInfoVO.getCodeKey());
        if (!StringUtils.hasLength(redisCodeValue) || !redisCodeValue.equals(userInfoVO.getCheckCode())) {
            // 验证码不正确
            return AjaxResult.fail(-1, "验证码错误");
        }
        // 清除 redis 中的验证码
        redisTemplate.opsForValue().set(userInfoVO.getCodeKey(), "");
        // 2.根据登录名查询到用户对象
        UserInfo userInfo = userService.getUserByLoginName(userInfoVO.getLoginname());
        // 3.判断用户对象是否为空,说明当前登录名不存在
        if (userInfo == null || userInfo.getUid() <= 0) {
            // 用户对象为null或者用户为无效对象
            return AjaxResult.fail(-1, "用户名或密码错误!");
        }
        // 4.如果用户对象不为空,使用用户输入的密码和用户对象的真实密码
        // 进行比较
        if (PasswordUtil.decrypt(userInfoVO.getPassword(), userInfo.getPassword())) {
            // 5.如果输入密码和真实的密码相同,将用户对象存储到 Session
            session.setAttribute(AppVar.SESSION_KEY_USERINFO, userInfo);
            userInfo.setPassword(""); // 传递对象之前,先将敏感信息密码去除
            return AjaxResult.succ(userInfo);
        } else {
            // 6.如果输入的密码和真实密码不相同,说明密码错误
            return AjaxResult.fail(-1, "用户名或密码错误!");
        }
    }

3. interface IUserService

java 复制代码
package com.example.myblog.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.myblog.model.UserInfo;

public interface IUserService extends IService<UserInfo> {
    int reg(UserInfo userInfo);
    UserInfo getUserByLoginName(String loginname);
}

4. interface UserMapper

java 复制代码
package com.example.myblog.dao;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.myblog.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

public interface UserMapper extends BaseMapper<UserInfo> {

    // 添加用户
    int reg(UserInfo userInfo);

    // 根据登录名查询用户对象
    UserInfo getUserByLoginName(@Param("loginname")String name);

}

5. UserMapper.xml

xml 复制代码
    <select id="getUserByLoginName" resultType="com.example.myblog.model.UserInfo">
        select * from userinfo
        where loginname=#{loginname}
    </select>

四、登录检验拦截器

1. LoginInterceptor

java 复制代码
package com.example.myblog.config;


/**
 * 登录判断的拦截器
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.先得到 HttpSession 对象
        HttpSession session = request.getSession(false);
        if(session!=null && session.getAttribute(AppVar.SESSION_KEY_USERINFO)!=null){
            // 已经登录
            return true;
        }
        // 代码执行到此处,说明用户未登录
        response.sendRedirect("/login.html");
        return false;
    }
}

2. MyConfig

java 复制代码
@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Resource
    private LoginInterceptor loginInterceptor;
    @Value("${imagepath}")
    private String imagepath;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/user/reg") // 排除注册接口(注册接口不拦截)
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/getuser")
                .excludePathPatterns("/user/getsess")
                .excludePathPatterns("/user/isartbyme")
                .excludePathPatterns("/getcaptcha")
                .excludePathPatterns("/art/list")
                .excludePathPatterns("/art/detail")
                .excludePathPatterns("/art/update_rcount")
                .excludePathPatterns("/comment/list")
                .excludePathPatterns("/**/*.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/image/**")
                .excludePathPatterns("/js/**");
    }
}

五、注销功能

1. 前端script

java 复制代码
    function logout() {
        // 1.先让用户确认是否真的退出系统
        if (confirm("是否确认退出?")) {
            // 2.如果点击的是"确认",发送 ajax 请求给后端(执行注销操作)
            jQuery.ajax({
                url: "/user/logout",
                type: "POST",
                data: {},
                success: function (res) {
                    // 3.最后,再将后端返回的结果呈现给用户
                    if (res.code == 200 && res.data == 1) {
                        // 退出成功
                        alert("退出成功!");
                        location.href = "/login.html"; // 跳转到登录页
                    } else {
                        alert("抱歉:操作失败,请重试!" + res.msg);
                    }
                }
            });
        }
    }

2. 后端 session中移除

java 复制代码
    @RequestMapping("/logout")
    public AjaxResult logout(HttpSession session) {
        try {
            // 清楚当前登录用户的 session 信息
            session.removeAttribute(AppVar.SESSION_KEY_USERINFO);
        } catch (Exception e) {
            return AjaxResult.fail(-1, "清楚失败");
        }
        return AjaxResult.succ(1);
    }

六、MyBatisPlus

1. 部署pom.xml
2. mapper继承extends BaseMapper
java 复制代码
package com.example.myblog.dao;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.myblog.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

public interface UserMapper extends BaseMapper<UserInfo> {

    // 添加用户
    int reg(UserInfo userInfo);

    // 根据登录名查询用户对象
    UserInfo getUserByLoginName(@Param("loginname")String name);

}
service实现 IUserService接口
3. interface IUserService extends IService
java 复制代码
package com.example.myblog.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.myblog.model.UserInfo;

public interface IUserService extends IService<UserInfo> {
    int reg(UserInfo userInfo);
    UserInfo getUserByLoginName(String loginname);
}
service.impl实现 UserServiceImpl
4. class UserServiceImpl extends ServiceImpl<UserMapper,UserInfo> implements IUserService
java 复制代码
package com.example.myblog.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.myblog.dao.UserMapper;
import com.example.myblog.model.UserInfo;
import com.example.myblog.service.IUserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,UserInfo> implements IUserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public int reg(UserInfo userInfo) {
        return userMapper.reg(userInfo);
    }

    @Override
    public UserInfo getUserByLoginName(String loginname) {
        return userMapper.getUserByLoginName(loginname);
    }
}
5. 实现分页功能
java 复制代码
@Component
@Order(1)
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.先得到 HttpSession 对象
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(AppVar.SESSION_KEY_USERINFO) != null) {
            // 已经登录
            return true;
        }
        // 代码执行到此处,说明用户未登录
        response.sendRedirect("/login.html");
        return false;
    }
}
java 复制代码
@Configuration
public class MyConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Value("${imagepath}")
    private String imagepath;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") // 拦截所有请求
                .excludePathPatterns("/user/reg") // 排除注册接口(注册接口不拦截)
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/getuser")
                .excludePathPatterns("/user/getsess")
                .excludePathPatterns("/user/isartbyme")
                .excludePathPatterns("/getcaptcha")
                .excludePathPatterns("/art/list")
                .excludePathPatterns("/art/detail")
                .excludePathPatterns("/art/update_rcount")
                .excludePathPatterns("/comment/list")
                .excludePathPatterns("/**/*.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/image/**")
                .excludePathPatterns("/js/**");

    }


    /**
     * 映射图片路径
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/image/**").addResourceLocations("file:" + imagepath);

    }
}
添加拦截器
通过page对象分页
java 复制代码
@Configuration
public class PageConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 将 MP 里面的分页插件设置 MP
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return interceptor;
    }
}

文章模块

七、文章添加功能

1. 前端页面

html 复制代码
<script>
    function mysub() {
        // alert(editor.getValue()); // 获取值
        // editor.setValue("#123") // 设置值

        // 1.得到控件,进行非空效验
        var title = jQuery("#title");
        if (title.val().trim() == "") {
            // 标题为空
            alert("请先输入标题!");
            title.focus();
            return false;
        }
        if (editor.getValue() == "") {
            // 内容为空
            alert("请先输入正文!");
            return false;
        }
        // 2.发送请求给后端
        jQuery.ajax({
            url: "/art/add",
            type: "POST",
            data: {
                "title": title.val().trim(),
                "content": editor.getValue()
            }, success: function (res) {
                // 3.将结果返回给前端的用户
                if (res.code == 200 && res.data == 1) {
                    // 文章添加成功
                    if (confirm("恭喜:添加成功!是否继续添加?")) {
                        // 继续添加文章
                        location.href = location.href; // 刷新当前页面
                    } else {
                        // 不添加文章,跳转到文章管理页面
                        location.href = "myblog_list.html"; // 跳转到我的文章管理页
                    }
                } else {
                    // 文章添加失败
                    alert("抱歉:操作失败!" + res.msg);
                }
            }
        });
    }
</script>
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>
</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="myblog_list.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") // 设置值

        // 1.得到控件,进行非空效验
        var title = jQuery("#title");
        if (title.val().trim() == "") {
            // 标题为空
            alert("请先输入标题!");
            title.focus();
            return false;
        }
        if (editor.getValue() == "") {
            // 内容为空
            alert("请先输入正文!");
            return false;
        }
        // 2.发送请求给后端
        jQuery.ajax({
            url: "/art/add",
            type: "POST",
            data: {
                "title": title.val().trim(),
                "content": editor.getValue()
            }, success: function (res) {
                // 3.将结果返回给前端的用户
                if (res.code == 200 && res.data == 1) {
                    // 文章添加成功
                    if (confirm("恭喜:添加成功!是否继续添加?")) {
                        // 继续添加文章
                        location.href = location.href; // 刷新当前页面
                    } else {
                        // 不添加文章,跳转到文章管理页面
                        location.href = "myblog_list.html"; // 跳转到我的文章管理页
                    }
                } else {
                    // 文章添加失败
                    alert("抱歉:操作失败!" + res.msg);
                }
            }
        });
    }
</script>
</body>

</html>

2. 实体类 ArticleInfo

java 复制代码
package com.example.myblog.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 文章的实体类
 */
@Setter
@Getter
@TableName("articleinfo")
public class ArticleInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = -52722551515977433L;
    @TableId(type = IdType.AUTO)
    private Long aid;
    private String title;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    @TableField("`desc`")
    private String desc;
    private String content;
    private int state;
    private long uid;
    private long rcount;
}

3. 文章传输类

java 复制代码
package com.example.myblog.model.vo;

import com.example.myblog.model.ArticleInfo;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;

@Setter
@Getter
public class ArticleInfoVO extends ArticleInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = -7122236306076080498L;
    private String photo;
    private String nickname;
    private int artCount;
}

4. controller中 add

java 复制代码
package com.example.myblog.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.myblog.model.ArticleInfo;
import com.example.myblog.model.UserInfo;
import com.example.myblog.model.vo.ArticleInfoVO;
import com.example.myblog.service.IArticleInfoService;
import com.example.myblog.util.AjaxResult;
import com.example.myblog.util.SessionUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/art")
public class ArticleController {
    @Resource
    private IArticleInfoService articleInfoService;

    // 文章简介的长度
    private final int DESC_LENGTH = 120;

    // 分页每页显示的最大条数
    private final int PAGE_PAGE_SIZE = 2;

    /**
     * 文章添加
     *
     * @param articleInfo
     * @param session
     * @return
     */
    @RequestMapping("/add")
    public AjaxResult add(ArticleInfo articleInfo, HttpServletRequest request) {
        // 1.非空判断
        if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle()) ||
                !StringUtils.hasLength(articleInfo.getContent())) {
            // 参数有误
            return AjaxResult.fail(-1, "参数有误");
        }
        // 获取作者id
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null) {
            return AjaxResult.fail(-2, "用户未登录");
        }
        // 设置作者
        articleInfo.setUid(userInfo.getUid());
        // 设置简介
        String content = articleInfo.getContent();
        if (content.length() > DESC_LENGTH) {
            content = content.substring(0, DESC_LENGTH);
        }
        articleInfo.setDesc(content);
        // 2.添加文章到数据库
        boolean result = articleInfoService.save(articleInfo);
        // 3.将上一步的结果返回给调用方
        if (result) {
            // 添加文章成功
            return AjaxResult.succ(1);
        } else {
            // 添加文章失败
            return AjaxResult.fail(-1, "操作失败,请重试!");
        }
    }
}

5. ArticleInfoMapper extends BaseMapper

java 复制代码
package com.example.myblog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.myblog.model.ArticleInfo;
import com.example.myblog.model.vo.ArticleInfoVO;
import org.apache.ibatis.annotations.Param;

public interface ArticleInfoMapper extends BaseMapper<ArticleInfo> {
    ArticleInfoVO getDetail(@Param("aid")Integer aid);

    int updateRCount(@Param("aid")Integer aid);
}

八、文章修改

1. 实现在url中查找id方法

js 复制代码
// 根据 url 中的参数获取对应的值 http(s)://xxxx?key=value&key2=value2
function getUrlParam(key){
    var urlparam = location.search; // ?key=value&key2=value2
    if(urlparam!="" && urlparam.indexOf("?")>=0){
        // url 有值
        urlparam = urlparam.substring(1); // key=value&key2=value2
        var paramArray = urlparam.split("&");
        for(var i=0;i<paramArray.length;i++){
            if(paramArray[i].indexOf("=")>0){
                var kv = paramArray[i].split("=");
                if(kv[0]==key){
                    return kv[1];
                }
            }
        }
    }
    return "";
}

2. 前端实现修改

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>
    <script src="js/urlutil.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="myblog_list.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>
    // 文章id(来自于url里面的参数)
    var aid = getUrlParam("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 //
        });
    }

    // initEdit("# 在这里写下一篇博客"); // 初始化编译器的值

    // 先查询数据库中的文章信息进行显示
    function initPage() {
        if (aid == null || aid == "") {
            alert("参数有误!");
            return false;
        }
        // 请求后端查询文章信息
        jQuery.ajax({
            url: "/art/getinfobyaid",
            type: "GET",
            data: {
                "aid": aid
            },
            success: function (res) {
                // 得到后端的结果并展示到当前页面
                if (res.code == 200 && res.data != null && res.data.aid > 0) {
                    // 查询到了文章信息
                    jQuery("#title").val(res.data.title);
                    initEdit(res.data.content);
                } else {
                    alert("抱歉:查询失败!" + res.msg);
                }
            }
        });
    }

    initPage();

    // 提交
    function mysub() {
        // alert(editor.getValue()); // 获取值
        // editor.setValue("#123") // 设置值

        // 1.参数效验
        var title = jQuery("#title");
        if (title.val().trim() == "") {
            alert("请先输入标题!");
            title.focus();
            return false;
        }
        if (editor.getValue() == "") {
            alert("请先输入正文!");
            return false;
        }
        // 2.将数据发送给后端【文章标题、文章正文、文章id】
        jQuery.ajax({
            url: "/art/update",
            type: "POST",
            data: {
                "aid": aid,
                "title": title.val(),
                "content": editor.getValue()
            },
            success: function (res) {
                // 3.将结果展现给用户
                if (res.code == 200 && res.data == 1) {
                    alert("恭喜:修改成功!");
                    location.href = "/myblog_list.html";
                } else {
                    alert("抱歉:操作失败,请重试!" + res.msg)
                }
            }
        });
    }
</script>
</body>

</html>

3. 后端

java 复制代码
    /**
     * @param aid
     * @return
     */
    @RequestMapping("/getinfobyaid")
    public AjaxResult getInfoByAid(Integer aid) {
        // 1.参数效验
        if (aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "参数有误!");
        }
        // 2.查询数据库
        ArticleInfo articleInfo = articleInfoService.getById(aid);
        // 3.将结果返回给前端
        return AjaxResult.succ(articleInfo);
    }

    /**
     * 修改操作
     *
     * @param articleInfo
     * @param request
     * @return
     */
    @RequestMapping("/update")
    public AjaxResult update(ArticleInfo articleInfo, HttpServletRequest request) {
        // 1.参数效验
        if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle()) ||
                !StringUtils.hasLength(articleInfo.getContent()) ||
                articleInfo.getAid() == null || articleInfo.getAid() <= 0) {
            // 参数有误
            return AjaxResult.fail(-1, "参数有误");
        }
        // 2.组装数据
        // 获取作者id
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null) {
            return AjaxResult.fail(-2, "用户未登录");
        }
//        // 设置作者
//        articleInfo.setUid(userInfo.getUid());
        // 设置简介
        String content = articleInfo.getContent();
        if (content.length() > DESC_LENGTH) {
            content = content.substring(0, DESC_LENGTH);
        }
        articleInfo.setDesc(content);
        // 3.执行数据库修改操作
        UpdateWrapper<ArticleInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("aid", articleInfo.getAid());
        updateWrapper.eq("uid", userInfo.getUid());  // 文章归属人效验
        boolean result = articleInfoService.update(articleInfo, updateWrapper);
        // 4.将结果返回给前端
        return AjaxResult.succ(result ? 1 : 0);
    }

九、文章分页功能

1. 前端分页

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>
    <script src="js/jquery.min.js"></script>
    <script src="js/urlutil.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_add.html">写博客</a>
    <!--    <a href="login.html">登陆</a>-->
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="container">
    <!-- 右侧内容详情 -->
    <div class="container-right" style="width: 100%;">
        <div id="artlist_div">


        </div>

        <hr>
        <div class="blog-pagnation-wrapper">
            <button class="blog-pagnation-item" onclick="doFirst()">首页</button>
            <button class="blog-pagnation-item" onclick="doBefore()">上一页</button>
            <button class="blog-pagnation-item" onclick="doNext()">下一页</button>
            <button class="blog-pagnation-item" onclick="doLast()">末页</button>
        </div>
    </div>
</div>
<script>
    // 每页显示条数
    var psize = 2;
    // 当前页码(当前在第几页)
    var pindex = getUrlParam("pindex");
    if (pindex == "") { // 修正当前页码
        pindex = 1;
    }
    // 分页最大页面
    var pages = 1;

    // 访问后端接口,获取列表数据
    function initPageList() {
        jQuery.ajax({
            url: "/art/list",
            type: "GET",
            data: {
                "pindex": pindex,
                "psize": psize
            },
            success: function (res) {
                if (res.code == 200 && res.data.records.length >= 0) {
                    // 查询到了数据,将数据展示在页面的列表中
                    pages = res.data.pages; // 分页最大条数
                    var listHtml = "";
                    for (let i = 0; i < res.data.records.length; i++) {
                        var art = res.data.records[i];
                        listHtml += '<div class="blog">';
                        listHtml += '<div class="title">' + art.title + '</div>';
                        listHtml += '<div class="date">' + art.createtime + '</div>';
                        listHtml += '<div class="desc">' + art.desc + '</div>';
                        listHtml += '<a href="blog_content.html?aid=' + art.aid + '" class="detail">查看全文 &gt;&gt;</a>';
                        listHtml += '</div>';
                    }
                    jQuery("#artlist_div").html(listHtml);
                } else if (res.code == 200 && res.data.length == 0) {
                    // 接口查询成功,但数据库中没有数据
                    pages = 1; // 分页最大条数
                    jQuery("#artlist_div").html("<h3 style='margin-left: 50px;margin-top: 50px;'>暂无数据!</h3>");
                } else {
                    alert("抱歉:查询失败!" + res.msg);
                }
            }
        });
    }

    initPageList();

    // 点击"首页"
    function doFirst() {
        // 判断是否已经在首页
        if (pindex <= 1) {
            alert("已经在首页了,不能再调转了。");
            return false;
        }
        // 跳转到首页
        location.href = "blog_list.html";
    }

    // 点击"末页"
    function doLast() {
        // 判断是否已经在末页
        if (pindex >= pages) {
            alert("已经在末页了,不能跳转了。");
            return false;
        }
        // 跳转到末页
        location.href = "blog_list.html?pindex=" + pages;
    }

    // 点击"上一页"
    function doBefore() {
        // 判断是否已经在首页
        if (pindex <= 1) {
            alert("已经在首页了,不能再调转了。");
            return false;
        }
        pindex = parseInt(pindex) - 1;
        // 跳转到上一页
        location.href = "blog_list.html?pindex=" + pindex;
    }

    // 点击"下一页"
    function doNext() {
        // 判断是否已经在末页
        // 判断是否已经在末页
        if (pindex >= pages) {
            alert("已经在末页了,不能跳转了。");
            return false;
        }
        pindex = parseInt(pindex) + 1;
        // 跳转
        location.href = "blog_list.html?pindex=" + pindex;
    }
</script>
</body>
</html>

2. 后端

java 复制代码
    /**
     * 文章的分页查
     *
     * @param pindex 页码(当前第几页)
     * @param psize  每页显示的最大条数
     * @return
     */
    @RequestMapping("/list")
    public AjaxResult getList(Integer pindex, Integer psize) {
        // 1.参数校正
        if (pindex == null || pindex <= 0) {
            // 无效的参数
            pindex = 1;
        }
        if (psize == null || psize <= 0) {
            // 无效的参数
            psize = PAGE_PAGE_SIZE;
        }
        // 2.查询数据库得到分页数据
        Page page = new Page(pindex, psize);
        Page<ArticleInfo> result = articleInfoService.page(page);
        // 3.将分页的数据返回给前端
        return AjaxResult.succ(result);
    }

十、加盐算法

  1. uuid生成一个salt
java 复制代码
package com.example.myblog.util;

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

import java.util.UUID;

/**
 * 密码加盐操作
 */
public class PasswordUtil {

    /**
     * 加盐算法 -> 格式:盐值(32)$加密之后的密码(32)
     *
     * @param password 原密码
     * @return
     */
    public static String encrypt(String password) {
        // 1.生成盐值
        String salt = UUID.randomUUID().toString().replace("-", "");
        // 2.使用加密算法将盐值+原密码进行加密
        String finalPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        // 3.将盐值和加密后的密码一起返回
        String dbPassword = salt + "$" + finalPassword;
        return dbPassword;
    }

    /**
     * 密码验证
     *
     * @param inputPassword 用户输入的密码
     * @param dbPassword    数据库中的密码
     * @return
     */
    public static boolean decrypt(String inputPassword, String dbPassword) {
        // 1.验证参数
        if (!StringUtils.hasLength(inputPassword) || !StringUtils.hasLength(dbPassword) ||
                dbPassword.length() != 65 || !dbPassword.contains("$")) {
            return false;
        }
        // 2.将用户输入的密码和数据库的盐值进行加密,得到待验证的加密密码
        // 2.1 得到盐值 & 最终正确的密码
        String[] dbPasswordArray = dbPassword.split("\\$");
        String salt = dbPasswordArray[0];
        String finalPassword = dbPasswordArray[1];
        // 2.2 使用数据库的盐值+用户输入的密码进行加密=待验证的加密密码
        String userPassword = DigestUtils.md5DigestAsHex((salt + inputPassword).getBytes());
        // 3.将待验证密的加密密码和数据的加密的密码进行对比
        if (userPassword.equals(finalPassword)) {
            return true;
        }
        // 4.将结果返回给调用方
        return false;
    }

    public static void main(String[] args) {
        String password = "123456";
//        System.out.println("加盐加密的值:"+encrypt(password));
//        System.out.println("加盐加密的值2:"+encrypt(password));
//        System.out.println("加盐加密的值3:"+encrypt(password));

        String inputPassword = "123456";
        String inputPassword2 = "456455";
        String dbPassword = "6d9104573c684c30ada25ec8ccdbb52d$5359762d43f9cd311a9fc2ea633abaec";

        System.out.println("正确的密码:" + decrypt(inputPassword, dbPassword));
        System.out.println("正确的密码:" + decrypt(inputPassword2, dbPassword));
    }
}

十一、登录信息保存到Redis

1. 配置pom.xml

2. 配置application

十二、验证码功能实现

  1. CaptchaUti生成一个验证码
  2. uuid生成一个验证码名称
  3. 图形验证码写出到一个文件中lineCaptcha.write(imagepath+uuid+".png");
  4. 构造验证码的url地址
  5. 将验证码存储到redis(uuid-code)
  6. 返回值是hashmap url+uuid
java 复制代码
@RestController
public class CaptchaController {

    @Value("${imagepath}")
    private String imagepath;
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/getcaptcha")
    public AjaxResult getCaptcha(){
        // 1.生成验证码到本地
        //定义图形验证码的长和宽
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(128, 50);
        String uuid = UUID.randomUUID().toString().replace("-","");
        // 图形验证码写出,可以写出到文件,也可以写出到流
        lineCaptcha.write(imagepath+uuid+".png");
        // url 地址
        String url = "/image/"+uuid+".png";
        // 将验证码存储到 redis
        redisTemplate.opsForValue().set(uuid,lineCaptcha.getCode());
        HashMap<String,String> result = new HashMap<>();
        result.put("codeurl",url);
        result.put("codekey",uuid);
        return AjaxResult.succ(result);
    }

}

1. 引入huto

2. controller

java 复制代码
package com.example.myblog.controller;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import com.example.myblog.util.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.UUID;

/**
 * 验证码的控制器
 */
@RestController
public class CaptchaController {

    @Value("${imagepath}")
    private String imagepath;
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/getcaptcha")
    public AjaxResult getCaptcha(){
        // 1.生成验证码到本地
        //定义图形验证码的长和宽
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(128, 50);
        String uuid = UUID.randomUUID().toString().replace("-","");
        // 图形验证码写出,可以写出到文件,也可以写出到流
        lineCaptcha.write(imagepath+uuid+".png");
        // url 地址
        String url = "/image/"+uuid+".png";
        // 将验证码存储到 redis
        redisTemplate.opsForValue().set(uuid,lineCaptcha.getCode());
        HashMap<String,String> result = new HashMap<>();
        result.put("codeurl",url);
        result.put("codekey",uuid);
        return AjaxResult.succ(result);
    }

}

3. 在拦截器中不拦截验证码,并且设置映射地址

java 复制代码
/**
 * 映射图片路径
 * @param registry
 */
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/image/**").addResourceLocations("file:" + imagepath);
}

4. 前端获取验证码

js 复制代码
    // 验证码key
    var codeKey = "";

    // 获取并显示验证码
    function loadCode() {
        jQuery.ajax({
            url: "/getcaptcha",
            type: "GET",
            data: {},
            success: function (res) {
                if (res.code = 200 && res.data != null && res.data != "") {
                    // 获取验证码成功
                    codeKey = res.data.codekey;
                    jQuery("#codeimg").attr("src", res.data.codeurl);
                }
            }
        });
    }

    loadCode();

5. 后端判断验证码是否有效

java 复制代码
        // redis 里面 key 对应的真是的验证码
        String redisCodeValue = (String) redisTemplate.opsForValue().get(userInfoVO.getCodeKey());
        if (!StringUtils.hasLength(redisCodeValue) || !redisCodeValue.equals(userInfoVO.getCheckCode())) {
            // 验证码不正确
            return AjaxResult.fail(-1, "验证码错误");
        }
        // 清除 redis 中的验证码
        redisTemplate.opsForValue().set(userInfoVO.getCodeKey(), "");

十三、文章详情页

文章详情页(1)查询文章和用户信息

1. 前端初始化详情信息
java 复制代码
// 初始化文章的详情信息
    function initArtDetail() {
        jQuery.ajax({
            url: "/art/detail",
            type: "GET",
            data: {
                "aid": aid
            },
            success: function (res) {
                if (res.code == 200 && res.data != null && res.data.aid >= 0) {
                    // 查询文章成功
                    var art = res.data;
                    jQuery("#title").html(art.title);
                    jQuery("#createtime").html(art.createtime);
                    jQuery("#rcount").html(art.rcount);
                    initEdit(art.content); // 设置文章详情
                    if (art.photo != null && art.photo != "") {
                        jQuery("#photo").attr("src", art.photo);
                    }
                    jQuery("#nickname").html(art.nickname);
                    jQuery("#artCount").html(art.artCount);
                } else {
                    alert("抱歉:操作失败,请重试!");
                }
            }
        });
    }
2. 创建传递对象 ArticleInfoVO
java 复制代码
package com.example.myblog.model.vo;

import com.example.myblog.model.ArticleInfo;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;

@Setter
@Getter
public class ArticleInfoVO extends ArticleInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = -7122236306076080498L;
    private String photo;
    private String nickname;
    private int artCount;
}
3. 后端
java 复制代码
    /**
     * 查询文章的详情
     *
     * @param aid
     * @return
     */
    @RequestMapping("/detail")
    public AjaxResult getDetail(Integer aid) {
        // 1.参数效验
        if (aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "非法参数!");
        }
        // 2.查询数据库中的文章信息
        ArticleInfoVO articleInfoVO = articleInfoService.getDetail(aid);
        if (articleInfoVO == null || articleInfoVO.getAid() <= 0) {
            return AjaxResult.fail(-2, "文章查询失败!");
        }
        // 3.组装数据:查询当前用户总共发表的文章数
        QueryWrapper<ArticleInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", articleInfoVO.getUid());
        articleInfoVO.setArtCount((int) articleInfoService.count(queryWrapper));
        // 4.将最终组装好的解决返回给前端
        return AjaxResult.succ(articleInfoVO);
    }
4. ArticleInfoServiceImpl 实现 getDetail 查询方法(联合查询)
java 复制代码
package com.example.myblog.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.myblog.model.ArticleInfo;
import com.example.myblog.model.vo.ArticleInfoVO;

public interface IArticleInfoService extends IService<ArticleInfo> {
    ArticleInfoVO getDetail(Integer aid);

    int updateRCount(Integer aid);
}
java 复制代码
package com.example.myblog.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.myblog.dao.ArticleInfoMapper;
import com.example.myblog.model.ArticleInfo;
import com.example.myblog.model.vo.ArticleInfoVO;
import com.example.myblog.service.IArticleInfoService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class ArticleInfoServiceImpl extends ServiceImpl<ArticleInfoMapper, ArticleInfo> implements IArticleInfoService {

    @Resource
    private ArticleInfoMapper articleInfoMapper;

    @Override
    public ArticleInfoVO getDetail(Integer aid) {
        return articleInfoMapper.getDetail(aid);
    }

    @Override
    public int updateRCount(Integer aid) {
        return articleInfoMapper.updateRCount(aid);
    }
}
5. mapper实现联合查询
xml 复制代码
    <select id="getDetail" resultType="com.example.myblog.model.vo.ArticleInfoVO">
        select a.*,u.photo,u.nickname from articleinfo a
            left join userinfo u on a.uid=u.uid
            where aid=#{aid}
    </select>

文章详情页(2)查询评论信息

1. 评论实体类
java 复制代码
package com.example.myblog.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Getter;
import lombok.Setter;

import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 评论实体类
 */
@Setter
@Getter
@TableName("commentinfo")
public class CommentInfo implements Serializable {
    @Serial
    private static final long serialVersionUID = -5849198667119918959L;
    @TableId(type= IdType.AUTO)
    private long cid;
    private long aid;
    private long uid;
    private String content;
    private LocalDateTime createtime;
}
2. 实现MP
java 复制代码
package com.example.myblog.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.myblog.model.CommentInfo;
import com.example.myblog.model.vo.CommentInfoVO;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface CommentInfoMapper extends BaseMapper<CommentInfo> {
    List<CommentInfoVO> getList(@Param("aid")Integer aid);
}
java 复制代码
package com.example.myblog.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.myblog.dao.CommentInfoMapper;
import com.example.myblog.model.CommentInfo;
import com.example.myblog.model.vo.CommentInfoVO;
import com.example.myblog.service.ICommentInfoService;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CommentInfoServiceImpl extends ServiceImpl<CommentInfoMapper, CommentInfo> implements ICommentInfoService  {

    @Resource
    private CommentInfoMapper commentInfoMapper;

    @Override
    public List<CommentInfoVO> getList(Integer aid) {
        return commentInfoMapper.getList(aid);
    }
}
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.myblog.dao.CommentInfoMapper">

    <select id="getList" resultType="com.example.myblog.model.vo.CommentInfoVO">
        SELECT c.*,u.nickname from commentinfo c
            left join userinfo u on c.uid=u.uid
            where aid=#{aid}
            order by cid desc
    </select>

</mapper>
3. 后端实现对文章评论的返回
java 复制代码
    /**
     * 查询某篇文章下的所有评论
     *
     * @param aid
     * @return
     */
    @RequestMapping("/list")
    public AjaxResult getList(Integer aid) {
        // 1.参数效验
        if (aid == null && aid <= 0) {
            return AjaxResult.fail(-1, "参数有误!");
        }
        // 2.查询数据库
        List<CommentInfoVO> list = commentInfoService.getList(aid);
        // 3.将结果返回给前端
        return AjaxResult.succ(list);
    }
4. 前端-加载评论列表
js 复制代码
    // 加载评论列表
    function initComment() {
        jQuery.ajax({
            url: "/comment/list",
            type: "GET",
            data: {
                "aid": aid
            },
            success: function (res) {
                if (res.code == 200 && res.data != null) {
                    var commentListHtml = "";
                    for (let i = 0; i < res.data.length; i++) {
                        var comment = res.data[i];
                        commentListHtml += '<div style="margin-bottom: 26px;">';
                        commentListHtml += comment.nickname + ':' + comment.content;
                        commentListHtml += '<br>';
                        commentListHtml += '<a class="comment_del_class" style="display: none;" href="javascript:del(' +
                            comment.cid + ')">删除</a>';
                        commentListHtml += '</div>';
                    }
                    jQuery("#commentlist").html(commentListHtml);
                }
            }
        });
    }

文章详情页(3)阅读量加1

1. mapper
java 复制代码
public interface ArticleInfoMapper extends BaseMapper<ArticleInfo> {
    ArticleInfoVO getDetail(@Param("aid")Integer aid);

    int updateRCount(@Param("aid")Integer aid);
}
2. mapper.xml
xml 复制代码
    <update id="updateRCount">
        update articleinfo set rcount=rcount+1
        where aid=#{aid}
    </update>
3. service
java 复制代码
package com.example.myblog.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.myblog.dao.ArticleInfoMapper;
import com.example.myblog.model.ArticleInfo;
import com.example.myblog.model.vo.ArticleInfoVO;
import com.example.myblog.service.IArticleInfoService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

@Service
public class ArticleInfoServiceImpl extends ServiceImpl<ArticleInfoMapper, ArticleInfo> implements IArticleInfoService {

    @Resource
    private ArticleInfoMapper articleInfoMapper;

    @Override
    public ArticleInfoVO getDetail(Integer aid) {
        return articleInfoMapper.getDetail(aid);
    }

    @Override
    public int updateRCount(Integer aid) {
        return articleInfoMapper.updateRCount(aid);
    }
}
java 复制代码
package com.example.myblog.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.myblog.model.ArticleInfo;
import com.example.myblog.model.vo.ArticleInfoVO;

public interface IArticleInfoService extends IService<ArticleInfo> {
    ArticleInfoVO getDetail(Integer aid);

    int updateRCount(Integer aid);
}
4. ArticleController
java 复制代码
    /**
     * 阅读量 +1
     *
     * @param aid
     * @return
     */
    @RequestMapping("/update_rcount")
    public AjaxResult updateRCount(Integer aid) {
        // 1.参数效验
        if (aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "参数有误!");
        }
        // 2.修改数据库
        int result = articleInfoService.updateRCount(aid);
        // 3.将结果返回给前端
        return AjaxResult.succ(result);
    }
5. 前端
java 复制代码
    function updateCount() {
        jQuery.ajax({
            url: "/art/update_rcount",
            type: "POST",
            data: {
                "aid": aid
            },
            success: function (res) {
            }
        });
    }

文章详情页(4)评论删除按钮展示

1. 判断当前文章是否属于当前登录用户(然后判断删除是否显示)
js 复制代码
    // 判断当前文章是否属于当前登录用户
    function isArtByMe(aid) {
        jQuery.ajax({
            url: "/user/isartbyme",
            type: "GET",
            data: {
                "aid": aid
            },
            success: function (res) {
                if (res.code == 200 && res.data == 1) {
                    // 当前文章归属于当前登录用户
                    jQuery(".comment_del_class").each(function (i) {
                        jQuery(this).show();
                    });
                }
            }
        });
    }
2. 获取当前登录的用户
java 复制代码
// 获取当前登录的用户
    function getSessUser() {
        jQuery.ajax({
            url: "/user/getsess",
            type: "GET",
            data: {},
            success: function (res) {
                if (res.code == 200 && res.data != null && res.data.uid >= 0) {
                    // 当前用户已经登录
                    islogin = true;
                    jQuery("#comment_login_name").html(res.data.nickname);
                    // 判断当前文章是否是当前登录用户发表的
                    isArtByMe(aid);
                } else {
                    // 当前用户未登录
                }
            }
        });
    }
3. 后端判断当前文章是否属于当前登录用户 || 并且获取当前登录用户
java 复制代码
@RequestMapping("/getsess")
    public AjaxResult getSess(HttpServletRequest request) {
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            return AjaxResult.fail(-2, "当前用户未登录!");
        }
        return AjaxResult.succ(userInfo);
    }

    /**
     * 查询当前文章是否属于当前登录用户
     */
    @RequestMapping("/isartbyme")
    public AjaxResult isArtByMe(Integer aid, HttpServletRequest request) {
        if (aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "参数有误!");
        }
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            return AjaxResult.fail(-2, "当前用户未登录!");
        }
        ArticleInfo articleInfo = articleInfoService.getById(aid);
        if (articleInfo != null && articleInfo.getAid() >= 0
                && articleInfo.getUid() == userInfo.getUid()) {
            // 文章是当前登录用户的
            return AjaxResult.succ(1);
        }
        return AjaxResult.succ(0);
    }

十四、添加和删除评论

1. 前端

js 复制代码
    // 添加评论
    function addComment() {
        // 评论正文
        var comment_content = jQuery("#comment_content");
        // 1.非空效验
        if (comment_content.val().trim() == "") {
            alert("请先输入评论的内容!");
            comment_content.focus();
            return false;
        }
        // 2.登录判断
        if (!islogin) {
            alert("请先登录!");
            return false;
        }
        // 3.将前端的数据发送给后端
        //    3.1 文章id
        //    3.2 评论内容
        jQuery.ajax({
            url: "/comment/add",
            type: "POST",
            data: {
                "aid": aid,
                "content": comment_content.val()
            },
            success: function (res) {
                // 4.将后端返回的数据显示给用户
                if (res.code == 200 && res.data == 1) {
                    // 评论添加成功
                    alert("恭喜:评论发表成功!");
                    // 刷新评论(刷新当前页面)
                    location.href = location.href;
                } else {
                    alert("抱歉:评论发表失败,请重试!" + res.msg);
                }
            }
        });
    }
js 复制代码
    // 评论的删除功能
    function del(cid) {
        if (!confirm("确定删除")) {
            return false;
        }
        // 1.效验参数
        if (cid == "" || cid <= 0) {
            alert("抱歉:操作失败,请刷新页面之后重试!");
            return false;
        }
        if (aid == "" || aid <= 0) {
            alert("抱歉:评论删除失败,请刷新页面之后重试!");
            return false;
        }
        // 2.发送数据给后端[评论id(cid)|文章id(aid)]
        jQuery.ajax({
            url: "/comment/del",
            type: "POST",
            data: {
                "cid": cid,
                "aid": aid
            },
            success: function (res) {
                // 3.将后端结果展现给用户
                if (res.code == 200 && res.data == 1) {
                    // 删除成功
                    alert("恭喜:评论删除成功!");
                    location.href = location.href;
                } else {
                    // 评论删除失败
                    alert("抱歉:评论删除失败!" + res.msg);
                }
            }
        });
    }

2. 后端

java 复制代码
    @RequestMapping("/add")
    public AjaxResult add(Long aid, String content, HttpServletRequest request) {
        // 1.参数效验
        if (aid == null || aid <= 0 || !StringUtils.hasLength(content)) {
            // 非法参数
            return AjaxResult.fail(-1, "非法参数");
        }
        // 2.组装数据(将 uid 从 session 中获取出来)
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            // 用户登录有问题
            return AjaxResult.fail(-2, "请先登录!");
        }
        CommentInfo commentInfo = new CommentInfo();
        commentInfo.setAid(aid);
        commentInfo.setUid(userInfo.getUid());
        commentInfo.setContent(content);
        // 3.将评论对象插入到数据库
        boolean result = commentInfoService.save(commentInfo);
        // 4.将数据库执行的结果返回给前端
        return AjaxResult.succ(result ? 1 : 0);
    }
java 复制代码
    @RequestMapping("/del")
    public AjaxResult del(Long cid, Long aid, HttpServletRequest request) {
        // 1.参数效验
        if (cid == null || cid <= 0 || aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "非法参数!");
        }
        // 2.效验权限(判断当前的文章是否属于当前登录用户)
        // 2.1 从 session 获取到用户 id
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            // 无效登录用户
            return AjaxResult.fail(-2, "请先登录!");
        }
        // 2.2 从文章中获取到用户 id
        ArticleInfo articleInfo = articleInfoService.getById(aid);
        if (articleInfo == null || articleInfo.getAid() <= 0) {
            return AjaxResult.fail(-3, "非法的文章id");
        }
        // 2.3 判断文章uid是否等于session uid
        //   如果相等,说明当前文章属于当前登录用户,可以进行删除操作
        //   否则是不能进行删除操作的
        if (userInfo.getUid() != articleInfo.getUid()) {
            return AjaxResult.fail(-4, "非法操作!");
        }
        // 3.删除文章(操作数据库)
        boolean result = commentInfoService.removeById(cid);
        // 4.将结果返回给前端
        return AjaxResult.succ(result ? 1 : 0);
    }

十五、个人中心

个人中心-1(修改头像)

1. 前端
js 复制代码
    // 获取用户头像和昵称
    function initPage() {
        jQuery.ajax({
            url: "/user/getsess",
            type: "GET",
            data: {},
            success: function (res) {
                if (res.code == 200 && res.data != null && res.data.uid >= 0) {
                    // 得到当前的登录用户
                    var userinfo = res.data;
                    if (null != userinfo.photo && userinfo.photo != "") {
                        jQuery("#photo").attr("src", userinfo.photo);
                    }
                    jQuery("#nickname").val(userinfo.nickname);
                } else {
                    alert("抱歉:查询用户信息出错,请刷新页面再试!" + res.msg);
                }
            }
        });
    }

    initPage();

    // 保存头像
    function savePhoto() {
        // 得到图片
        var photo = jQuery("#file")[0].files[0];
        if (photo == null) {
            alert("请先选择要上传的头像!");
            return false;
        }
        // 构建一个 form 表单
        var formData = new FormData();
        formData.append("file", photo);
        jQuery.ajax({
            url: "/user/save_photo",
            type: "POST",
            data: formData,
            processData: false,
            contentType: false,
            success: function (res) {
                if (res.code == 200 && res.data != null && res.data != "") {
                    // 图片上传成功
                    jQuery("#photo").attr("src", res.data);
                } else {
                    // 图片上传失败
                    alert("抱歉:上传图片失败,请重试!" + res.msg);
                }
            }
        });
    }
2. 后端
java 复制代码
    @RequestMapping("/save_photo")
    public AjaxResult savePhoto(MultipartFile file, HttpServletRequest request) {
        // 1.保存图片到服务器
        // 得到图片的后缀
        String imageType = file.getOriginalFilename().
                substring(file.getOriginalFilename().lastIndexOf("."));
        // 生成图片的名称
        String imgName = UUID.randomUUID().toString()
                .replace("-", "") + imageType;
        try {
            file.transferTo(new File(imagePath + imgName));
        } catch (IOException e) {
            return AjaxResult.fail(-1, "图片上传失败!");
        }
        String imgUrl = "/image/" + imgName;
        // 2.将图片地址保存到数据库
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            // 未登录
            return AjaxResult.fail(-2, "请先登录!");
        }
        UpdateWrapper<UserInfo> wrapper = new UpdateWrapper<>();
        wrapper.set(true, "photo", imgUrl);
        wrapper.eq("uid", userInfo.getUid());
        boolean result = userService.update(wrapper);
        if (result) {
            // 图片保存成功
            // 更新头像到 session 中
            userInfo.setPhoto(imgUrl);
            HttpSession session = request.getSession();
            session.setAttribute(AppVar.SESSION_KEY_USERINFO, userInfo);
            return AjaxResult.succ(imgUrl);
        } else {
            return AjaxResult.fail(-3, "数据库修改失败");
        }
    }

修改昵称和密码

1. 前端
js 复制代码
    // 修改昵称或修改密码
    function updateUser() {
        var isUpdatePassword = false; // 是否修改密码
        // 1.非空效验
        var nickname = jQuery("#nickname");
        var oldPassword = jQuery("#old_password");
        var password = jQuery("#password");
        var password2 = jQuery("#password2");
        if (nickname.val().trim() == "") {
            alert("请先输入昵称!");
            nickname.focus();
            return false;
        }
        if (oldPassword.val() != "" ||
            password.val() != "" || password2.val() != "") {
            // 需要修改密码
            isUpdatePassword = true;
            if (oldPassword.val().trim() == "") {
                alert("请先输入原密码!");
                oldPassword.focus();
                return false;
            }
            if (password.val().trim() == "") {
                alert("请先输入新密码!");
                password.focus();
                return false;
            }
            if (password2.val().trim() == "") {
                alert("请先输入确认密码!");
                password2.focus();
                return false;
            }
            // 判断新密码和确认密码是否一致
            if (password.val() != password2.val()) {
                alert("两次输入的新密码不一致,请先确认!");
                return false;
            }
        }
        // 2.将前端的数据提交给后端
        jQuery.ajax({
            url: "/user/update",
            type: "POST",
            data: {
                "nickname": nickname.val(),
                "oldpassword": oldPassword.val(),
                "password": password.val(),
                "isUpdatePassword": isUpdatePassword
            },
            success: function (res) {
                // 3.将返回的结果展现给用户
                if (res.code == 200 && res.data == 1) {
                    // 修改成功
                    if (isUpdatePassword) {
                        alert("恭喜:修改成功,请重新登录!");
                        // 修改密码,重新登录
                        location.href = "login.html";
                    } else {
                        alert("恭喜:昵称修改成功!");
                    }
                } else {
                    // 修改失败
                    alert("抱歉:修改失败,请重试!" + res.msg);
                }
            }
        });
    }
2. 后端
java 复制代码
    /**
     * 个人中心修改昵称或密码
     *
     * @param nickname
     * @param oldpassword
     * @param password
     * @param isUpdatePassword
     * @return
     */
    @RequestMapping("/update")
    public AjaxResult update(String nickname, String oldpassword,
                             String password, Boolean isUpdatePassword,
                             HttpServletRequest request) {
        // 1.参数效验
        if (!StringUtils.hasLength(nickname)) {
            return AjaxResult.fail(-1, "非法参数!");
        }
        // 是否要修改密码
        if (isUpdatePassword) {
            // 修改原密码
            if (!StringUtils.hasLength(oldpassword) || !StringUtils.hasLength(password)) {
                return AjaxResult.fail(-1, "非法参数!");
            }
        }
        // 2.组装数据(从 Session 获取用户信息)
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null || userInfo.getUid() <= 0) {
            return AjaxResult.fail(-2, "请先登录!");
        }
        userInfo.setNickname(nickname);
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        // 判断是否修改密码,如果修改密码,则需要验证用户输入的原密码是否正确
        if (isUpdatePassword) {
            UserInfo dbUser = userService.getById(userInfo.getUid());
            // 需要修改密码,验证原密码是否正确
            boolean checkPassword = PasswordUtil.decrypt(oldpassword, dbUser.getPassword());
            if (!checkPassword) {
                return AjaxResult.fail(-3, "原密码输入错误!");
            }
            // 修改密码
            password = PasswordUtil.encrypt(password); // 加盐之后的密码
            updateWrapper.set("password", password);
        }
        // 3.修改数据库
        updateWrapper.eq("uid", userInfo.getUid());
        updateWrapper.set("nickname", nickname);
        boolean result = userService.update(updateWrapper);
        // 4.将结果返回给前端
        return AjaxResult.succ(result ? 1 : 0);
    }

十六、我的列表页

1. 查询所有文章

java 复制代码
    // 查询我的所有文章
    function initPage() {
        jQuery.ajax({
            url: "/art/mylist",
            type: "GET",
            data: {},
            success: function (res) {
                var createHtml = "";
                if (res.code == 200 && res.data != null && res.data.length > 0) {
                    // 我已经发表文章了
                    for (let i = 0; i < res.data.length; i++) {
                        let art = res.data[i];
                        createHtml += '<div class="blog">';
                        createHtml += '<div class="title">' + art.title + '</div>';
                        createHtml += '<div class="date">' + art.createtime + '</div>';
                        createHtml += '<div class="desc">';
                        createHtml += art.desc;
                        createHtml += '</div>';
                        createHtml += '<a href="blog_content.html?aid=' +
                            art.aid + '" class="detail">查看全文 &gt;&gt;</a>&nbsp;&nbsp;';
                        createHtml += '<a href="blog_edit.html?aid=' +
                            art.aid + '" class="detail">修改 &gt;&gt;</a>&nbsp;&nbsp;';
                        createHtml += '<a href="javascript:del(' +
                            art.aid + ')" class="detail">删除 &gt;&gt;</a>';
                        createHtml += '</div>';
                    }
                } else {
                    createHtml = "<h2 style='margin-left: 50px;margin-top: 20px;'>暂无数据,请先添加!</h2>";
                }
                jQuery("#artlist").html(createHtml);
            }
        });
    }

    initPage();
java 复制代码
    /**
     * 获取当前登录用户的所有文章
     *
     * @return
     */
    @RequestMapping("/mylist")
    public AjaxResult myList(HttpServletRequest request) {
        // 1.得到当前登录用户
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null) {
            return AjaxResult.fail(-1, "请先登录!");
        }
        // 2.查询当前用户发表的所有文章
        QueryWrapper<ArticleInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("uid", userInfo.getUid());
        queryWrapper.orderByDesc(true, "aid");
        List<ArticleInfo> list = articleInfoService.list(queryWrapper);
        // 3.将结果返回给前端
        return AjaxResult.succ(list);
    }

2. 删除文章

java 复制代码
    // 删除文章的方法
    function del(aid) {
        if (confirm("确定删除?")) {
            // 1.效验参数合法性
            if (aid == "" || aid <= 0) {
                alert("非法参数,请重试!");
                return false;
            }
            // 2.调用后端执行删除
            jQuery.ajax({
                url: "/art/del",
                type: "POST",
                data: {
                    "aid": aid
                },
                success: function (res) {
                    // 3.得到后端的结果展示给用户
                    if (res.code == 200 && res.data == 1) {
                        // 删除成功
                        alert("恭喜:删除成功!");
                        location.href = location.href; // 刷新当前页面
                    } else {
                        alert("抱歉:操作失败!" + res.msg);
                    }
                }
            });
        }
    }
java 复制代码
    /**
     * 删除文章
     *
     * @param aid
     * @return
     */
    @RequestMapping("/del")
    public AjaxResult del(Long aid, HttpServletRequest request) {
        // 1.效验参数
        if (aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "非法参数!");
        }
        // 2.效验文章的归属人(不是我的文章是不能删除的)
        UserInfo userInfo = SessionUtil.getUserInfo(request);
        if (userInfo == null) {
            return AjaxResult.fail(-2, "请先登录!");
        }
        ArticleInfo articleInfo = articleInfoService.getById(aid);
        if (articleInfo == null) {
            return AjaxResult.fail(-1, "非法参数!");
        }
        if (articleInfo.getUid() != userInfo.getUid()) {
            // 当前文章不属于当前登陆用户,不能删除
            return AjaxResult.fail(-3, "不能删除别人的文章!");
        }
        // 3.执行数据库的删除操作
        boolean result = articleInfoService.removeById(aid);
        // 4.返回给前端
        return AjaxResult.succ(result ? 1 : 0);
    }

3. 退出

js 复制代码
    function logout() {
        // 1.先让用户确认是否真的退出系统
        if (confirm("是否确认退出?")) {
            // 2.如果点击的是"确认",发送 ajax 请求给后端(执行注销操作)
            jQuery.ajax({
                url: "/user/logout",
                type: "POST",
                data: {},
                success: function (res) {
                    // 3.最后,再将后端返回的结果呈现给用户
                    if (res.code == 200 && res.data == 1) {
                        // 退出成功
                        alert("退出成功!");
                        location.href = "/login.html"; // 跳转到登录页
                    } else {
                        alert("抱歉:操作失败,请重试!" + res.msg);
                    }
                }
            });
        }
    }

十七、并发编程

1. 线程池的创建

java 复制代码
package com.example.myblog.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class ThreadPoolConfig {
    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        taskExecutor.setMaxPoolSize(10);
        taskExecutor.setQueueCapacity(100000);
        taskExecutor.setThreadNamePrefix("my-threadpool-");
        taskExecutor.initialize();
        return taskExecutor;
    }
}

2. 后端优化

java 复制代码
    @RequestMapping("/total_init")
    public AjaxResult totalInit(Integer aid, HttpServletRequest request) throws ExecutionException, InterruptedException {
        // 1.参数效验
        if (aid == null || aid <= 0) {
            return AjaxResult.fail(-1, "非法参数!");
        }
        // 2.创建多个任务,放到线程池中进行执行
        // 2.1 得到文章详情信息
        FutureTask<ArticleInfoVO> getArtinfo = new FutureTask<>(() -> {
            // 查询数据库中的文章信息
            ArticleInfoVO articleInfoVO = articleInfoService.getDetail(aid);
            if (articleInfoVO != null) {
                // 组装数据:查询当前用户总共发表的文章数
                QueryWrapper<ArticleInfo> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("uid", articleInfoVO.getUid());
                articleInfoVO.setArtCount((int) articleInfoService.count(queryWrapper));
            }
            return articleInfoVO;
        });
        // 2.2 得到当前文章的评论列表
        FutureTask<List<CommentInfoVO>> getCommList = new FutureTask<>(() -> {
            return commentInfoService.getList(aid);
        });
        // 2.3 当前文章阅读量 ++
        FutureTask<Integer> upRCount = new FutureTask(() -> {
            return articleInfoService.updateRCount(aid);
        });
        // 2.4 当前登录用户的信息返回给前端
        FutureTask<UserInfo> getSess = new FutureTask<>(() -> {
            return SessionUtil.getUserInfo(request);
        });
        // 将任务放到线程池中并发执行
        taskExecutor.submit(getArtinfo);
        taskExecutor.submit(getCommList);
        taskExecutor.submit(upRCount);
        taskExecutor.submit(getSess);
        // 3.组装并发执行的结果,返回给前端
        ArticleInfoVO articleInfoVO = getArtinfo.get();
        List<CommentInfoVO> commentList = getCommList.get();
        UserInfo userInfo = getSess.get();
        // 组装并发任务返回的结果
        HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("articleinfo", articleInfoVO);
        resultMap.put("commentList", commentList);
        resultMap.put("userinfo", userInfo);
        return AjaxResult.succ(resultMap);

3. 前端接口优化

html 复制代码
    // 总的初始化页面
    function initPage() {
        // 执行总的详情页初始化
        jQuery.ajax({
            url: "/art/total_init",
            type: "POST",
            data: {
                "aid": aid
            },
            success: function (res) {
                if (res.code == 200 && res.data != null) {
                    // 初始化文章详情
                    initArtDetail(res.data.articleinfo);
                    // 加载评论列表
                    initComment(res.data.commentList);
                    // 查询当前登录用户的信息
                    getSessUser(res.data.userinfo);
                }
            }
        });

    }
相关推荐
疑惑的杰瑞8 分钟前
[乱码]确保命令行窗口与主流集成开发环境(IDE)统一采用UTF-8编码,以规避乱码问题
java·c++·vscode·python·eclipse·sublime text·visual studio
自身就是太阳10 分钟前
深入理解 Spring 事务管理及其配置
java·开发语言·数据库·spring
喵手14 分钟前
Java零基础-多态详解
java·开发语言·python
麋鹿会飞但不飘24 分钟前
EasyExcel拿表头(二级表头)爬坑,invokeHeadMap方法
java·spring boot·excel
Gauss松鼠会26 分钟前
GaussDB关键技术原理:高弹性(四)
java·大数据·网络·数据库·分布式·gaussdb
世俗ˊ31 分钟前
微服务-- Sentinel的使用
java·微服务·sentinel
alex18011 小时前
python实现多个pdf文件合并
java·python·pdf
七禾页话1 小时前
Java并发常见面试题(上)
java·jvm
Aries2631 小时前
Spring事务传播行为详解
java·数据库·spring
陌上少年,且听这风吟1 小时前
【已解决】SpringBoot3项目整合Druid依赖:Druid监控页面404报错
java·spring boot·spring