文章目录
- 1.博客系统项目页面展示
- 1.1博客登录页
- 1.2博客详情页
- 1.3博客发布页
- 1.4博客列表页
- 1.5博客编辑页
- 2.博客系统功能设计
- 2.1SQL数据库建库建表
- 2.1.1建库
- 2.1.2建用户表、博客表
- 2.1.3插入数据
- 2.1.4sql功能实现
- 2.1.5sql单元测试
- 2.2目录模块搭建
- 2.2.1controller、service、mapper三者之间的关系:
- 2.2.2model目录下的Result实体类 和 common目录下的Constants公共实体类 code errorMsg data
- 2.2.3config(配置类)目录下
- 2.2.3.1ResponseAdvice.java 统一返回结果 配置类
- 2.2.3.2ErrorAdvice.java统一异常处理 配置类
- 2.2.4utils目录下 DateUtils.java 工具类
- 2.3实现博客列表页
- 2.3.1前后端交互接口
- 2.3.2controller目录下 BlogController.java 控制层
- 2.3.3service目录下 BlogService.java 业务层
- 2.3.4 blog_list.html 博客列表页前端代码展示
- 2.4实现博客详情页
- 2.4.1前后端交互接口
- 2.4.2controller目录下 BlogController.java 控制层
- 2.4.3service目录下 BlogService.java 业务层
- 2.4.4 blog_detail.html 博客详情页前端代码展示
- 2.5实现博客登录页
- 2.5.1传统登录存在的问题 - 使用令牌技术
- 2.5.1.1 令牌技术
- 2.5.1.2 JWT令牌
- 2.5.1.3为什么要使用令牌
- 2.5.1.4使用JWT令牌
- 2.5.2前后端交互接口
- 2.5.3controller目录下 UserController.java 控制层
- 2.5.4service目录下 UserService.java 业务层
- 2.5.5 blog_login.html 博客登录页前端代码展示
- 2.5.6 utils下的 JwtUtils 工具类(使用JWT令牌)
- 2.5.7 config目录下 LoginInterceptor.java类 使用拦截器校验token
- 2.5.8config目录下 WebConfig.java类 拦截器的添加
- 2.5.9添加使用token全流程
- 2.6实现博客发布页
- 2.6.1前后端交互接口
- 2.6.2controller目录下 BlogController.java 控制层
- 2.6.3service目录下 BlogService.java 业务层
- 2.6.4 blog_edit.html 博客登录页前端代码展示
- 2.7实现博客编辑页
- 2.7.1前后端交互接口
- 2.7.2controller目录下 BlogController.java 控制层
- 2.7.3service目录下 BlogService.java 业务层
- 2.7.4 blog_update.html 博客登录页前端代码展示
- 2.8实现博客删除功能
- 2.8.1 mapper层中的实现
- 2.8.2controller目录下 BlogController.java 控制层
- 2.8.3service目录下 BlogService.java 业务层
- 2.8.4 blog_detail.html 博客登录页前端代码展示
- 2.9实现显示用户登录信息
- 2.9.1前后端交互接口
- 2.9.2获取登录用户实现信息
- 2.10实现用户退出
- 2.11加密/加盐
- 2.11.2盐值+密文如何获取
- 2.11.1 UserController.java
- 2.12 多平台配置
- 2.12.1配置目录
- jquary:
- 设置日志文件的文件名
大家好,我是晓星航。今天为大家带来的是 博客系统-spring版本 相关的讲解!😀
1.博客系统项目页面展示
1.1博客登录页
1.2博客详情页
1.3博客发布页
1.4博客列表页
1.5博客编辑页
2.博客系统功能设计
2.1SQL数据库建库建表
2.1.1建库
-- 建表SQL
create database if not exists java_blog_spring charset utf8mb4;
2.1.2建用户表、博客表
-- 用户表
DROP TABLE IF EXISTS java_blog_spring.user;
CREATE TABLE java_blog_spring.user(
id INT NOT NULL AUTO_INCREMENT,
user_name VARCHAR(128)NOT NULL,
password VARCHAR(128)NOT NULL,
github_url VARCHAR(128)NULL,
delete_flag TINYINT(4)NULL DEFAULT 0,
create_time DATETIME DEFAULT now(),
update_time DATETIME DEFAULT now(),
PRIMARY KEY(id),
UNIQUE INDEX user_name_UNIQUE (user_name ASC ))ENGINE = INNODB DEFAULT CHARACTER SET =utf8mb4 COMMENT ='用户表';
-- 博客表
drop table if exists java_blog_spring.blog;
CREATE TABLE java_blog_spring.blog(
id INT NOT NULL AUTO_INCREMENT,
title VARCHAR(200) NULL,
content TEXT NULL,
user_id INT(11)NULL,
delete_flag TINYINT(4)NULL DEFAULT 0,
create_time DATETIME DEFAULT now(),
update_time DATETIME DEFAULT now(),
PRIMARY KEY(id))
ENGINE =InnoDB DEFAULT CHARSET =utf8mb4 COMMENT ='博客表';
2.1.3插入数据
-- 新增用户信息
insert into java_blog_spring.user (user_name,
password,github_url) values ("zhangsan","3daf6c1523cf465f97afee9f06e4e014015a4e9e8519f728e20a9cd6b2bb4123","https://gitee.com/xiao-xinghang/java-warehouse");
insert into java_blog_spring.user (user_name,
password,github_url) values ("lisi","84f37ec4949b407bbde0373dd648c26c37843422e00a0c1b764762ae77aa3144","https://gitee.com/xiao-xinghang/java-warehouse");
insert into java_blog_spring.user (user_name,
password,github_url) values ("qiqi","ee655f9484e14762ae807013e283cdcbdd43ac2bf5f5788cbecfba159f82a2d7","https://gitee.com/xiao-xinghang/java-warehouse");
-- 新增博客信息
insert into java_blog_spring.blog (title,content,user_id) values ("第一篇博客","111我是博客正文我是博客正文我是博客正文",1);
insert into java_blog_spring.blog (title,content,user_id) values ("第二篇博客","222我是博客正文我是博客正文我是博客正文",2);
2.1.4sql功能实现
根据用户名,查询用户信息:

根据用户ID,查询用户信息:

获取博客列表:

根据博客ID,获取博客详情:

根据博客ID,编辑博客:

根据博客ID, 删除博客:

博客添加:

2.1.5sql单元测试
UserInfoMapper.java的单元测试:


BlogInfoMapper.java的单元测试:


2.2目录模块搭建

2.2.1controller、service、mapper三者之间的关系:
Controller负责接收用户请求,并通过调用Server层来处理业务逻辑,最终将结果返回给用户;Server层负责处理业务逻辑,并调用Mapper来读写数据库;Mapper负责数据的读取、写入和映射等操作。这样的架构可以使代码逻辑清晰,各层之间的职责分离,提高代码的可维护性和可扩展性。
简要理解就是:
图解:
2.2.2model目录下的Result实体类 和 common目录下的Constants公共实体类 code errorMsg data
定义一个Result,设置一个接口返回状态code,接口返回结果data,以及错误原因errorMsg,T为泛型。

成功方法success 一个data参数,类型为T泛型
失败方法fail 一个参数 返回result包含 code状态,errorMsg错误信息
失败方法fail 两个参数 返回result包含 code状态,errorMsg错误信息 以及 data结果
Constants.java:

定义一个成功返回值200,和一个失败返回值-1.
2.2.3config(配置类)目录下
2.2.3.1ResponseAdvice.java 统一返回结果 配置类

AOP统一处理返回结果
2.2.3.2ErrorAdvice.java统一异常处理 配置类

2.2.4utils目录下 DateUtils.java 工具类

该目录下代码的作用为将日期格式转化为我们希望的 2024-01-17 11:29
的格式
可根据下表中对应的日期举例,编写格式转化要求代码

再更改一下BlogInfo.java中的get方法,我们自己写一个,便能使时间变为我们需要的格式了

2.3实现博客列表页
2.3.1前后端交互接口

2.3.2controller目录下 BlogController.java 控制层
获取博客列表:

2.3.3service目录下 BlogService.java 业务层
获取博客列表:

2.3.4 blog_list.html 博客列表页前端代码展示

75行,这段代码是使用 jQuery 来将名为 “finalHtml” 的 HTML 内容设置到页面中所有类名为 “right” 的元素中。

此时 $(".right").html(finalHtml);
的作用就是把类似图示内容按照finalHtml的格式拼接起来,并以字符串的格式储存。但是图示里面是写死的代码所以只会固定展示,当我们输入新的数据时,这段代码便会自动将新数据拼接起来
2.4实现博客详情页
2.4.1前后端交互接口

2.4.2controller目录下 BlogController.java 控制层
获取博客详情:

2.4.3service目录下 BlogService.java 业务层
获取博客详情:

2.4.4 blog_detail.html 博客详情页前端代码展示
最开始通过right标签写死的前端代码

修改后,通过ajax动态输入的数据

通过 $(".operating").html(finalHtml);
它选择了类名为 “operating” 的元素,并将名为 “finalHtml” 的 HTML 内容设置到这些元素中,它会将 “finalHtml” 的内容设置到所有类名为 “operating” 的元素中。
2.5实现博客登录页
2.5.1传统登录存在的问题 - 使用令牌技术
传统思路:
存在问题:
session存储在服务器内存中。所以,服务器重启, session就丢失了,用户刚登录完, 就遇到了服务器重启,用户需要重新登录(体验)
解决方案:
2.5.1.1 令牌技术
令牌其实就是一个用户身份的标识,名称起的很高大上,其实本质就是一个字符串
比如我们出行在外,会带着自己的身份证,需要验证身份时,就掏出身份证
身份证不能伪造,可以辨别真假.

服务器具备生成令牌和验证令牌的能力
我们使用令牌技术,继续思考上述场景:
- 用户登录 用户登录请求,经过负载均衡,把请求转给了第一台服务器,第一台服务器进行账号密码验证,验证成功后,生成一个令牌,并返回给客户端.
- 客户端收到令牌之后,把令牌存储起来,可以存储在Cookie中,也可以存储在其他的存储空间(比如localStorage)
- 查询操作 用户登录成功之后,携带令牌继续执行査询操作,比如查询博客列表.此时请求转发到了第二台机器,第二台机器会先进行权限验证操作,服务器验证令牌是否有效,如果有效,就说明用户已经执行了登录操作如果令牌是无效的,就说明用户之前未执行登录操作.
令牌的优缺点
优点:
-
解决了集群环境下的认证问题
-
减轻服务器的存储压力(无需在服务器端存储)
缺点:
需要自己实现(包括令牌的生成,令牌的传递,令牌的校验)
当前企业开发中,解决会话跟踪使用最多的方案就是令牌技术
2.5.1.2 JWT令牌
令牌本质就是一个字符串,他的实现方式有很多,我们采用一个]WT令牌来实现.
介绍
JWT全称: JSON Web Token
官网: https://jwt.io/
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),用于客户端和服务器之间传递安全可靠的信息.其本质是一个token,是一种紧凑的URL安全方法
JWT组成
JWT由三部分组成,每部分中间使用点(.)分隔,比如:aaaaa.bbbbb.cccc
-
Header(头部) 头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
-
Payload(负载) 负载部分是存放有效信息的地方,里面是一些自定义内容,比如:
["userId":"123","userName":"zhangsan"}
,也可以存在jt提供的现场字段,比如exp(过期时间戳)等
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容,
- Signature(签名) 此部分用于防止iwt内容被篡改,确保安全性
防止被篡改,而不是防止被解析.
JWT之所以安全,就是因为最后的签名.jwt当中任何一个字符被篡改,整个令牌都会校验失败.
就好比我们的身份证,之所以能标识一个人的身份,是因为他不能被篡改,而不是因为内容加密.(任何人都可以看到身份证的信息,jwt 也是)
2.5.1.3为什么要使用令牌
服务多机部署,session不能用了
解决方式通常有两类:
1)把session放在一个公共的地方(机器),比如redis 服务端
2)使用token(更加常见) 客户端
token - 具有特定含义,且不能伪造的字符串
2.5.1.4使用JWT令牌
引入依赖:
<!-- jwt依赖--><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred --><version>0.11.5</version><scope>runtime</scope></dependency>
在test目录下创建JwtUtilTest.java使用令牌:
package com.example.blog;import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import org.junit.jupiter.api.Test;import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;public class JwtUtilTest {//过期时间: 30分钟private static final long expiration = 30 * 60 * 1000;private static final String secretString = "5CRMLhF7dQnOLCNjJw8dawYK2zTUxS4jDgUW2L99Tdo=";private static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));//1. 生成token//2. 验证token@Testpublic void genToken(){Map<String, Object> claim = new HashMap<>();claim.put("id",5);claim.put("name","zhangsan");String token = Jwts.builder().setClaims(claim).setExpiration(new Date(System.currentTimeMillis()+expiration)).signWith(key).compact();System.out.println(token);}@Testpublic void genKey(){//随机生成一个keySecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);String key = Encoders.BASE64.encode(secretKey.getEncoded());System.out.println(key);}@Testpublic void parseToken(){String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6NSwiZXhwIjoxNzA1NjM1NjU3fQ.jK1jhAsFGWRb6ZVZm6PPoVZp_bzsTT_i2Y3zUyMyvcU";JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();Claims body = build.parseClaimsJws(token).getBody();System.out.println(body);}
}

分析代码:
1.genToken()方法解析:
这里的三个设置方法,和下图网页中一样,先设置id,name,再设置签名,最后生成token。

2.使用getKey()方法随机生成token
3.使用parseToken()方法来解析token,获取token中的信息
2.5.2前后端交互接口
之前流程:
token的方式:
2.5.3controller目录下 UserController.java 控制层
package com.example.blog.controller;import com.example.blog.model.Result;
import com.example.blog.model.UserInfo;
import com.example.blog.service.UserService;
import com.example.blog.utils.JwtUtils;
import com.example.blog.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/login")public Result login(String userName, String password){/*** 1. 参数校验* 2. 密码校验* 3. 生成token, 并返回*/if (!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)){return Result.fail("用户名或密码为空");}//获取数据库中的密码UserInfo userInfo = userService.queryByName(userName);if (userInfo==null || userInfo.getId()<0){return Result.fail("用户不存在");}
// if (!password.equals(userInfo.getPassword())){
// return Result.fail("密码错误!");
// }if (!SecurityUtils.verify(password, userInfo.getPassword())){return Result.fail("密码错误!");}//生成token, 并返回Map<String, Object> claim = new HashMap<>();claim.put("id", userInfo.getId());claim.put("name", userInfo.getUserName());String token = JwtUtils.genToken(claim);return Result.success(token);}/*** 获取登录用户的信息*/@RequestMapping("/getUserInfo")public UserInfo getUserInfo(HttpServletRequest request){//1. 从token中获取用户ID//2. 根据用户ID, 获取用户信息String token = request.getHeader("user_token");Integer userId = JwtUtils.getUserIdFromToken(token);if (userId==null){return null;}return userService.queryById(userId);}/*** 获取当前作者的信息*/@RequestMapping("/getAuthorInfo")public UserInfo getAuthorInfo(Integer blogId){if (blogId==null && blogId<0){return null;}return userService.getAuthorInfo(blogId);}
}


2.5.4service目录下 UserService.java 业务层

2.5.5 blog_login.html 博客登录页前端代码展示
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客登陆页</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/login.css"></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系统</span><div class="space"></div><a class="nav-span" href="blog_list.html">主页</a><a class="nav-span" href="blog_edit.html">写博客</a></div><div class="container-login"><div class="login-dialog"><h3>登陆</h3><div class="row"><span>用户名</span><input type="text" name="username" id="username"></div><div class="row"><span>密码</span><input type="password" name="password" id="password"></div><div class="row"><button id="submit" onclick="login()">提交</button></div></div></div><script src="js/jquery.min.js"></script><script>function login() {// location.assign("blog_list.html");$.ajax({type:"post",url: "/user/login",data:{userName: $("#username").val(),password: $("#password").val()},success:function(result){if(result.code==200 && result.data!=null){//密码正确//存储tokenlocalStorage.setItem("user_token",result.data);location.href = "blog_list.html";}else{alert(result.errorMsg);}}});}</script>
</body></html>

2.5.6 utils下的 JwtUtils 工具类(使用JWT令牌)
package com.example.blog.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;import java.security.Key;
import java.util.Date;
import java.util.Map;/*** Jwt工具类*/
@Slf4j
public class JwtUtils {//过期时间: 1小时private static final long expiration = 60 * 60 * 1000;private static final String secretString = "5CRMLhF7dQnOLCNjJw8dawYK2zTUxS4jDgUW2L99Tdo=";private static final Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretString));public static String genToken(Map<String, Object> claim){String token = Jwts.builder().setClaims(claim) //设置载荷信息.setExpiration(new Date(System.currentTimeMillis()+expiration)) //设置过期时间.signWith(key) //设置签名.compact();return token;}public static Claims parseToken(String token){if (token==null){return null;}JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();Claims claims = null;try {claims = build.parseClaimsJws(token).getBody();}catch (Exception e){
// e.printStackTrace();log.error("解析token失败, token:"+token);}return claims;}public static Integer getUserIdFromToken(String token){Claims claims = parseToken(token);if (claims==null){return null;}return (Integer) claims.get("id");}// public static void main(String[] args) {
// String token = "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6MSwiZXhwIjoxNzA1ODAyNzUyfQ.atKGO-q32Dy5PYfFZZgoogHwbkULivFR_7sXF1Dw-so";
// Claims claims = parseToken(token);
// System.out.println(claims);
// }}
生成token:

解析token:

2.5.7 config目录下 LoginInterceptor.java类 使用拦截器校验token
拦截器(Interceptor)是一种特殊的组件,它可以在请求处理的过程中对请求和响应进行拦截和处理。拦截器可以在请求到达目标处理器之前、处理器处理请求之后以及视图渲染之前执行特定的操作。拦截器的主要目的是在不修改原有代码的情况下,实现对请求和响应的统一处理。
utils工具类中对parseToken解析token方法的实现:

2.5.8config目录下 WebConfig.java类 拦截器的添加

通过在common.js中添加ajax发送请求,达到给所有前端页面统一添加功能的目的,不需要一个一个写document功能了
2.5.9添加使用token全流程
后端负责生成token和验证token,前端负责存储token
eg:公安局负责发放和验证身份证,但是不负责存取身份证
2.6实现博客发布页
2.6.1前后端交互接口
2.6.2controller目录下 BlogController.java 控制层
发布博客:
&& 一假为假
|| 一真为真
2.6.3service目录下 BlogService.java 业务层
发布博客:
2.6.4 blog_edit.html 博客登录页前端代码展示
blog_edit.html:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客发布页</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/edit.css"><link rel="stylesheet" href="blog-editormd/css/editormd.css" /></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系统</span><div class="space"></div><a class="nav-span" href="blog_list.html">主页</a><a class="nav-span" href="blog_edit.html">写博客</a><a class="nav-span" href="#" onclick="logout()">注销</a></div><div class="content-edit"><div class="push"><input type="text" name="" id="title"><input type="button" value="发布文章" id="submit" onclick="submit()"></div><!-- markdown 插件 html代码 --><div id="editor"><textarea style="display:none;" id="content" name="content">##在这里写下一篇博客</textarea></div></div><script src="js/jquery.min.js"></script><script src="blog-editormd/editormd.min.js"></script><script src="js/common.js"></script><script type="text/javascript">$(function () {var editor = editormd("editor", {width: "100%",height: "550px",path: "blog-editormd/lib/"});});function submit() {$.ajax({type: "post",url: "/blog/add",data: {title: $("#title").val(),content:$("#content").val()},success:function(result){if(result.code==200){location.href = "blog_list.html";}else{alert(result.error);}}});}</script>
</body></html>

如果成功的的话,且code状态为200,那么会跳转到blog_list.html页面中,失败就会返回result的错误码
2.7实现博客编辑页
2.7.1前后端交互接口
2.7.2controller目录下 BlogController.java 控制层

2.7.3service目录下 BlogService.java 业务层

2.7.4 blog_update.html 博客登录页前端代码展示
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>博客详情页</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/detail.css"></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系统</span><div class="space"></div><a class="nav-span" href="blog_list.html">主页</a><a class="nav-span" href="blog_edit.html">写博客</a><a class="nav-span" href="#" onclick="logout()">注销</a></div><div class="container"><div class="left"><div class="card"><img src="pic/Jay.jpg" alt=""><h3></h3><a href="#">GitHub 地址</a><div class="row"><span>文章</span><span>分类</span></div><div class="row"><span>2</span><span>1</span></div></div></div><div class="right"><div class="content"><div class="title"></div><div class="date"></div><div class="detail" id="detail" style="background-color: transparent;"></div><div class="operating"></div></div></div></div><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="blog-editormd/css/editormd.css" /><script src="js/jquery.min.js"></script><script src="blog-editormd/lib/marked.min.js"></script><script src="blog-editormd/lib/prettify.min.js"></script><script src="blog-editormd/editormd.js"></script><script src="js/common.js"></script><script>//获取博客详情$.ajax({type: "get",url: "/blog/getBlogDetail" + location.search,success: function (result) {if (result.code == 200 && result.data != null) {var blog = result.data;$(".title").text(blog.title);$(".date").text(blog.createTime);// $(".detail").text(blog.content);editormd.markdownToHTML("detail", {markdown: blog.content,});//判断是否显示编辑/删除按钮if(blog.loginUser){var finalHtml = "";finalHtml +='<button οnclick="window.location.href=\'blog_update.html?blogId='+blog.id+'\'">编辑</button>';finalHtml +='<button οnclick="deleteBlog('+blog.id+')">删除</button>';console.log(finalHtml);$(".operating").html(finalHtml);}else{}}},error: function (error) {//用户未登录if (error != null && error.status == 401) {alert("用户未登录");location.href = "blog_login.html";}}});//显示博客作者信息getUserInfo("/user/getAuthorInfo" + location.search);function deleteBlog(blogId) {$.ajax({type: "post",url: "/blog/delete"+location.search,success:function(result){if(result.code==200 && result.data==true){location.href ="blog_list.html";}}});}</script>
</body></html>
判断是否显示编辑/删除按钮
错误情况,跳转到博客登录页面
点击更新按钮时更新文章,包括博客ID,文章标题,以及文章内容,更新完成后跳转到博客列表页

2.8实现博客删除功能
2.8.1 mapper层中的实现
第一种方式为设置delete_flag为1来实现删除博客功能
第二种方式为直接删除序号为id的博客的功能
2.8.2controller目录下 BlogController.java 控制层

2.8.3service目录下 BlogService.java 业务层

2.8.4 blog_detail.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/common.css"><link rel="stylesheet" href="css/detail.css"></head><body><div class="nav"><img src="pic/logo.jpg" alt=""><span class="blog-title">我的博客系统</span><div class="space"></div><a class="nav-span" href="blog_list.html">主页</a><a class="nav-span" href="blog_edit.html">写博客</a><a class="nav-span" href="#" onclick="logout()">注销</a></div><div class="container"><div class="left"><div class="card"><img src="pic/Jay.jpg" alt=""><h3></h3><a href="#">GitHub 地址</a><div class="row"><span>文章</span><span>分类</span></div><div class="row"><span>2</span><span>1</span></div></div></div><div class="right"><div class="content"><div class="title"></div><div class="date"></div><div class="detail" id="detail" style="background-color: transparent;"></div><div class="operating"></div></div></div></div><!-- 引入 editor.md 的依赖 --><link rel="stylesheet" href="blog-editormd/css/editormd.css" /><script src="js/jquery.min.js"></script><script src="blog-editormd/lib/marked.min.js"></script><script src="blog-editormd/lib/prettify.min.js"></script><script src="blog-editormd/editormd.js"></script><script src="js/common.js"></script><script>//获取博客详情$.ajax({type: "get",url: "/blog/getBlogDetail" + location.search,success: function (result) {if (result.code == 200 && result.data != null) {var blog = result.data;$(".title").text(blog.title);$(".date").text(blog.createTime);// $(".detail").text(blog.content);editormd.markdownToHTML("detail", {markdown: blog.content,});//判断是否显示编辑/删除按钮if(blog.loginUser){var finalHtml = "";finalHtml +='<button οnclick="window.location.href=\'blog_update.html?blogId='+blog.id+'\'">编辑</button>';finalHtml +='<button οnclick="deleteBlog('+blog.id+')">删除</button>';console.log(finalHtml);$(".operating").html(finalHtml);}else{}}},error: function (error) {//用户未登录if (error != null && error.status == 401) {alert("用户未登录");location.href = "blog_login.html";}}});//显示博客作者信息getUserInfo("/user/getAuthorInfo" + location.search);function deleteBlog(blogId) {$.ajax({type: "post",url: "/blog/delete"+location.search,success:function(result){if(result.code==200 && result.data==true){location.href ="blog_list.html";}}});}</script>
</body></html>
实现删除功能,删除成功后跳转到博客列表页

2.9实现显示用户登录信息
2.9.1前后端交互接口
2.9.2获取登录用户实现信息
controller层的UserController.java:

service层中的UserService.java类

utils工具目录中的 JwtUtils.java 工具类

2.10实现用户退出
common.js:

在common.js中写入代码,然后再所有前端代码中引入这个js,他就会实现写一次代码可多次重复引入使用,它通过清除token来实现
2.11加密/加盐
加密算法:y = f(x)
不可逆的,无法解密,通常用来做校验
检验数据是否正确:如果两个字符串加密后的密文一样,我们就认为明文是一样
-
对称密码算法 是指加密秘钥和解密秘钥相同的密码算法,常见的对称密码算法有:AES,DES,3DES,RC4,RC5,RC6等.
-
非对称密码算法 是指加密秘钥和解密秘钥不同的密码算法,该算法使用一个秘钥进行加密,用另外一个秘钥进行解密,
- 加密秘钥可以公开,又称为 公钥
- 解密秘钥必须保密,又称为 私钥
常见的非对称密码算法有:RSA,DSA,ECDSA,ECC 等
- 摘要算法 是指把任意长度的输入消息数据转化为固定长度的输出教据的一种密码算法,摘要算法是不可逆的,也就是无法解密,通常用来检验数据的完整性的重要技术,即对数据进行哈希计算然后比较摘要值,判断是否一致.常见的摘要算法有: MD5,SHA系列(SHA1,SHA2等),CRC(CRC8,CRC16,CRC32)
使用MD5加密时,同样的字符串在每个平台加密得到的结果都是一样的
例如:
MD5算法图解:

博主项目中采取的加密算法就是MD5+密文(盐值+密文),直接引用DigestUtils包
2.11.2盐值+密文如何获取
圈红的那部分就是 盐值+密文 加密后的密码,将他覆盖到数据库中的password位置,即可实现加密算法,我们输入密码还是输入初始值,例如上图初始密码是123456,此时我们数据库中密码虽然很复杂是 盐值+密文 加密之后的,但是我们登录时只需要输入初始密码即可
2.11.1 UserController.java
检验密码是否正确
2.12 多平台配置
2.12.1配置目录
配置目录文件详情:
application.yml:
spring:profiles:active: @profile.name@
pom.xml:

这里通过在xml文件中添加两个yml文件

然后通过在主配置xml文件中的active中 添加 @profile.name@
即可随意切换xml文件了

这里点dev和prod前面小方框,即可轻松实现添加或者删除xml文件。
application-dev.yml:
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8&useSSL=falseusername: rootpassword: 111111driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰自动转换log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句mapper-locations: classpath:mapper/**Mapper.xml
# 设置日志文件的文件名
logging:file:name: logger/spring-blog.log
application-prod.yml:
server:port: 9090tomcat:uri-encoding: UTF-8max-http-header-size: 8096
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8&useSSL=falseusername: rootpassword: 111111driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰自动转换mapper-locations: classpath:mapper/**Mapper.xml
# 设置日志文件的文件名
logging:file:name: /var/logger/spring-blog.log
application-test.yml:
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8&useSSL=falseusername: rootpassword: 111111driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰自动转换mapper-locations: classpath:mapper/**Mapper.xml
# 设置日志文件的文件名
logging:file:name: /var/logger/spring-blog.log
jquary:
lse
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰自动转换
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
mapper-locations: classpath:mapper/**Mapper.xml
设置日志文件的文件名
logging:
file:
name: logger/spring-blog.log
application-prod.yml:```yml
server:port: 9090tomcat:uri-encoding: UTF-8max-http-header-size: 8096
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8&useSSL=falseusername: rootpassword: 111111driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰自动转换mapper-locations: classpath:mapper/**Mapper.xml
# 设置日志文件的文件名
logging:file:name: /var/logger/spring-blog.log
application-test.yml:
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/java_blog_spring?characterEncoding=utf8&useSSL=falseusername: rootpassword: 111111driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰自动转换mapper-locations: classpath:mapper/**Mapper.xml
# 设置日志文件的文件名
logging:file:name: /var/logger/spring-blog.log
感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘