项目概述
项目名称:知识共享小屋
项目描述:该博客系统使用Spring Boot框架开发,旨在实现用户注册、登录、查看、修改、发布、删除文章、强制登录等功能。
开发环境:MacOS15、IDEA专业版、JDK17、Navicat、Termius
应用技术:Spring 、SpringBoot、MyBatis、MySQL、JWT、Ajax
一、项目架构(框架、配置文件)
项目框架
工欲善其事,必先利其器
因此先将项目架构搭建好,主要分为controller、mapper、model、config、util、、constants、interceptor几个包,并没有太过于复杂的逻辑便省略service。
YML配置
相对于properties,yml采用缩进来表示结构层次,使其更具有可读性
# 数据库配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis配置
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # mybatis打印执行SQL语句配置
map-underscore-to-camel-case: true #自动驼峰转换 eg: user_name -> userName
# 日志配置
logging:
file: # 储存打印日志
name: logs/blog.log
二、数据库设计 (结构、SQL、实体)
结构设计
SQL语句
创建数据库代码放在resource文件夹下,方便后期在云服务器上创建数据库,将SQL语句放在Navicat中执行。
-- 创建数据库
CREATE DATABASE IF NOT EXISTS blog CHARACTER SET utf8mb4;
-- 使用数据库
USE blog;
-- 用户表
DROP TABLE IF EXISTS user;
CREATE TABLE user (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_name` VARCHAR(128) NOT NULL,
`password` VARCHAR(128) NOT NULL,
`github` VARCHAR(128) NULL,
`delete_flag` TINYINT(4) NULL DEFAULT 0, -- 采用物理删除方式,默认0显示,1不显示
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -- 更改后时间
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = '用户表';
-- 博客表
DROP TABLE IF EXISTS article;
CREATE TABLE article (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` VARCHAR(255) NOT NULL,
`content` TEXT NOT NULL, -- 使用TEXT类型存储文章内容
`user_id` INT NOT NULL, -- 文章作者id
`delete_flag` TINYINT(4) NULL DEFAULT 0, -- 采用物理删除方式,默认0显示,1不显示
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- 更改后时间
FOREIGN KEY (user_id) REFERENCES user(id) -- 通过user_id作为外键和user表中id关联,从而查看作者信息
) DEFAULT CHARACTER SET = utf8mb4 COMMENT = '文章表';
-- 新增用户信息
INSERT INTO user (user_name, password, github) VALUES ("zhangsan", "123456", "https://gitee.com/1");
INSERT INTO user (user_name, password, github) VALUES ("lisi", "123456", "https://gitee.com/2");
-- 新增博客文章
INSERT INTO article (title, content, user_id) VALUES ("第一篇博客", "111我是博客正文我是博客正文我是博客正文", 1);
INSERT INTO article (title, content, user_id) VALUES ("第二篇博客", "222我是博客正文我是博客正文我是博客正文", 2);
实体类
在model文件下创建user、article实体类,进行数据封装便于对数据库数据进行管理和操作,按照数据表进行变量创建。
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private String github;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}
@Data
public class BlogInfo {
private Integer id;
private String title;
private String content;
private Integer userId;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
private boolean isAuthor;
public String getCreateTime() {
//重写get方法,对默认时间进行格式化
return DateUtils.format(createTime);
}
}
注:在BlogInfo表中调用DataUtils中format方法,对createTime的get方法进行重写,确保以正确的时间格式显示。
封装数据库操作
先将大体需要调用数据库哪些操作进行编写,方便后续调用,同样分为user和blog操作进行划分,在mapper下创建UserMapper、BlogMapper。
UserMapper:
@Mapper
public interface UserMapper {
//注册用户插入数据库user表中
@Insert("insert into user(user_name, password) values (#{userName}, #{password})")
Integer insertUser(String userName, String password);
//通过id查询用户信息
@Select("select * from user where delete_flag = 0 and id = #{id}")
UserInfo selectById(Integer id);
//通过用户名查询用户信息,验证用户密码是否正确
@Select("select * from user where delete_flag = 0 and user_name = #{userName}")
UserInfo selectByUserName(String userName);
}
BlogMapper:
@Mapper
public interface BlogMapper {
//查询所有博客信息
@Select("select * from article where delete_flag = 0")
List<BlogInfo> selectAllBlog();
//通过博客id查询博客信息
@Select("select * from article where delete_flag = 0 and id = #{blogId}")
BlogInfo selectByBlogId(Integer blogId);
//将默认delete_flag的0,修改成1进行物理删除
@Update("update article set delete_flag = 1 where id = #{blogId}")
boolean delete(Integer blogId);
//将博客信息插入到blog表中
@Insert("insert into article(title, content, user_id) values (#{title}, #{content}, #{userId})")
Integer insertBlog(BlogInfo blogInfo);
//更新博客信息需要判断当前参数是否为空,通过mybatis的注解方式来实现此方法过于繁琐,不利于查看,通常使用xml的方式进行编写
//在此只有一个数据更改较为繁琐,便不使用xml方式
@Update({
"<script>",
"UPDATE article",
"<set>",
"<if test='title != null'>title = #{title},</if>",
"<if test='content != null'>content = #{content},</if>",
"<if test='userId != null'>user_id = #{userId},</if>",
"<if test='deleteFlag != null'>delete_flag = #{deleteFlag},</if>",
"</set>",
"WHERE id = #{id}",
"</script>"
})
Integer updateBlog(BlogInfo blogInfo);
}
三、Utils(Date、Security、JWT)
DateUtil
数据库中current_timestamp默认时间格式为 2024-07-15T14:20:00Z格式,因此要将其转换为 YY-MM-DD HH-mm-ss格式,通过java.text下的SimpleDateFormat方法来进行格式转换。
public class DateUtils {
// 定义时间默认格式
private static final String DEFAULT_DATE_TIME = "yyyy-MM-dd HH:mm:ss";
//根据默认格式返回时间
public static String format(Date date) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DEFAULT_DATE_TIME);
return simpleDateFormat.format(date);
}
}
SecurityUtils
用户隐私信息在数据库中以明文的方式进行存储,数据库信息泄漏隐私显而易见,通过md5的加密方式来对数据进行存储。此项目中对用户注册密码进行加密,用户登录进行校验。
public class SecurityUtils {
/**
* 将注册密码和通过UUID随机生成的盐值(salt)进行mad加密,返回salt目的方便后续解密操作
* @param password
* @return salt + md5(salt + password)
*/
public static String encrypt(String password) {
String salt = UUID.randomUUID().toString().replace("-", "");
String result = DigestUtils.md5DigestAsHex((salt + password).getBytes());
return salt + result;
}
/**
* 加密方法由salt + md5(salt + password)组成六十四位字符进行存储,因此前32位则是salt,
* salt为固定不变的,在将salt和新输入的password进行md5加密,
* 最后将salt和加密后的密码拼接在一起与数据库中存储的加密密码进行比比对,相同则密码正确。
* @param password
* @param sqlPassword
* @return boolean
*/
public static boolean verify(String password, String sqlPassword) {
if(!StringUtils.hasLength(password)) {
return false;
}
if(sqlPassword == null || sqlPassword.length() != 64) {
return false;
}
String salt = sqlPassword.substring(0, 32);
String result =