个人博客系统项目大全【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);
                }
            }
        });

    }
相关推荐
面朝大海,春不暖,花不开11 分钟前
自定义Spring Boot Starter的全面指南
java·spring boot·后端
得过且过的勇者y11 分钟前
Java安全点safepoint
java
夜晚回家1 小时前
「Java基本语法」代码格式与注释规范
java·开发语言
斯普信云原生组1 小时前
Docker构建自定义的镜像
java·spring cloud·docker
wangjinjin1801 小时前
使用 IntelliJ IDEA 安装通义灵码(TONGYI Lingma)插件,进行后端 Java Spring Boot 项目的用户用例生成及常见问题处理
java·spring boot·intellij-idea
wtg44521 小时前
使用 Rest-Assured 和 TestNG 进行购物车功能的 API 自动化测试
java
白宇横流学长1 小时前
基于SpringBoot实现的大创管理系统设计与实现【源码+文档】
java·spring boot·后端
fat house cat_2 小时前
【redis】线程IO模型
java·redis
stein_java3 小时前
springMVC-10验证及国际化
java·spring
weixin_478689763 小时前
C++ 对 C 的兼容性
java·c语言·c++