一、数据库设计
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.效验参数的正确性 2.密码加盐 3.验证验证码是否正确 4.调用服务执行添加操作(添加到数据库) 5.将执行结果返回给前端
- 登录功能:1.非空效验 2.验证验证码 3.清除验证码 4.根据登录名查询到用户对象 5.判断用户对象是否为空,说明当前登录名不存在 6.如果用户对象不为空,使用用户输入的密码和用户对象的真实密码 7.如果正确 加入到session
- 退出功能:1.在session中去除
- 获取
二、注册功能
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;">
<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;">
<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">查看全文 >></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);
}
十、加盐算法
- 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
十二、验证码功能实现
- CaptchaUti生成一个验证码
- uuid生成一个验证码名称
- 图形验证码写出到一个文件中lineCaptcha.write(imagepath+uuid+".png");
- 构造验证码的url地址
- 将验证码存储到redis(uuid-code)
- 返回值是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">查看全文 >></a> ';
createHtml += '<a href="blog_edit.html?aid=' +
art.aid + '" class="detail">修改 >></a> ';
createHtml += '<a href="javascript:del(' +
art.aid + ')" class="detail">删除 >></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);
}
}
});
}