【Java】从0实现一个基于SpringBoot的个人博客系统

从0实现一个基于SpringBoot的个人博客系统

  • 项目介绍
  • 准备工作
    • 数据准备
    • 创建项目
    • 准备前端页面
    • 编写配置文件
  • 项目公共模块
    • 实体类
    • 公共层
    • 业务代码
      • 持久层
      • 实现博客列表
      • 实现博客列表
        • 约定前后端交互接口
      • 实现博客详情
        • 约定前后端交互接口
        • 实现服务器代码
      • 实现登录
        • JWT令牌
          • JWT令牌生成和校验
          • 实现用户登录
            • 约定前后端交互接⼝
            • 实现服务器代码
      • 实现强制要求登录
        • 添加拦截器
      • 实现显示用户信息
        • 约定前后端交互接口
        • 实现服务器代码
      • 实现发布博客
        • 实现服务器代码
      • 实现删除/编辑博客
        • 约定前后端交互接口
          • 实现服务器代码
      • 加密/加盐
        • 写加密/解密工具类

项目介绍

使⽤SSM框架实现⼀个简单的博客系统
共5个⻚⾯

  1. ⽤⼾登录
  2. 博客发表⻚
  3. 博客编辑⻚
  4. 博客列表⻚
  5. 博客详情⻚

功能描述:
⽤⼾登录成功后, 可以查看所有⼈的博客. 点击 <<查看全⽂>> 可以查看该博客的正⽂内容. 如果该博客作者为当前登录⽤⼾, 可以完成博客的修改和删除操作, 以及发表新博客

页面预览

用户登录

在这里插入图片描述

博客详情
在这里插入图片描述
博客列表
在这里插入图片描述

博客发布
在这里插入图片描述

准备工作

数据准备

建表sql

-- 建表SQL
create database if not exists java_blog_spring charset utf8mb4;
-- ⽤⼾表
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 CHARACT 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 = '博客表';

创建项目

创建SpringBoot项⽬, 添加Spring MVC 和MyBatis对应依赖

准备前端页面

编写配置文件

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: spring-blog.log

项目公共模块

项⽬分为控制层(Controller), 服务层(Service), 持久层(Mapper).
在这里插入图片描述

实体类

实体类主要有Blog和User

@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 isLoginUser;public String getCreateTime() {return DateUtils.formatDate(createTime);}
}
@Data
public class UserInfo {private Integer id;private String userName;private String password;private String githubUrl;private Integer deleteFlag;private Date createTime;private Date updateTime;
}

公共层

  1. 统一返回结果实体类

业务状态码:200 业务处理成功 -1业务处理失败 -2用户未登录
msg:业务处理失败时,返回的错误信息
data:业务返回数据

@Data
public class Result<T> {private int code;private String errorMsg;private T data;public static <T> Result<T> success(T data){Result result = new Result();result.setCode(Constants.RESULT_SUCCESS);result.setData(data);result.setErrorMsg("");return result;}public static <T> Result<T> fail(String errorMsg){Result result = new Result();result.setCode(Constants.RESULT_FAIL);result.setErrorMsg(errorMsg);return result;}public static <T> Result<T> fail(String errorMsg, T data){Result result = new Result();result.setCode(Constants.RESULT_FAIL);result.setErrorMsg(errorMsg);result.setData(data);return result;}
}
  1. 统一返回结果
/*** 统一返回结果*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if(body instanceof Result){return body;}if(body instanceof String){ObjectMapper objectMapper = new ObjectMapper();return objectMapper.writeValueAsString(Result.success(body));}return Result.success(body);}
}
  1. 统一异常处理
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {@ExceptionHandlerpublic Result errorHandler(Exception e){Result result = new Result<>();result.setErrorMsg("内部错误,请联系管理员");result.setCode(Constants.RESULT_FAIL);return result;}
}

业务代码

持久层

根据需求, 先⼤致计算有哪些DB相关操作, 完成持久层初步代码, 后续再根据业务需求进⾏完善

  1. ⽤⼾登录⻚

a. 根据⽤⼾名查询⽤⼾信息

  1. 博客列表⻚

a. 根据id查询user信息 b. 获取所有博客列表

  1. 博客详情⻚

a. 根据博客ID查询博客信息 b. 根据博客ID删除博客(修改delete_flag=1)

  1. 博客修改⻚

a. 根据博客ID修改博客信息

  1. 发表博客

a. 插⼊新的博客数据

实现博客列表

@Mapper
public interface BlogInfoMapper {/*** 获取博客列表*/@Select("select * from blog where delete_flag = 0 order by create_time desc")List<BlogInfo> queryBlogList();/***根据博客id 获取博客详情*/@Select("select * from blog where id = #{id} and delete_flag = 0")BlogInfo queryById(Integer id);/*** 编辑博客*/@Update("update blog set title = #{title}, content = #{content} where id = #{id}")Integer update(BlogInfo blogInfo);/*** 删除博客*/@Update("update blog set delete_flag = 1 where id = #{id}")Integer deleteBlog(Integer id);/*** 添加博客*/@Insert("insert into blog(title,content,user_id) values(#{title}, #{content}, #{userId})")Integer insertBlog(BlogInfo blogInfo);}
@Mapper
public interface UserInfoMapper {/*** 根据用户名 查询用户信息*/@Select("select * from user where user_name = #{userName} and delete_flag = 0")UserInfo queryByName(String userName);/*** 根据用户ID 查询用户信息*/@Select("select * from user where id = #{id} and delete_flag = 0")UserInfo queryById(Integer id);
}

实现博客列表

约定前后端交互接口

[请求] /blog/getlist
[响应]
{
“code”: 200,
“msg”: “”,
“data”: [{
“id”: 1,
“title”: “第⼀篇博客”,
“content”: “111我是博客正⽂我是博客正⽂我是博客正⽂”,
“userId”: 1,
“deleteFlag”: 0,
“createTime”: “2023-10-21 16:56:57”,
“updateTime”: “2023-10-21T08:56:57.000+00:00”
},

]
}
客户端给服务器发送一个/blog/getList的HTTP请求 服务器给客户端返回一个JSON格式的数据

@RequestMapping("/getList")public List<BlogInfo> getBlogList() {return blogService.getBlogList();}
public List<BlogInfo> getBlogList(){return blogInfoMapper.queryBlogList();}

实现博客详情

⽬前点击博客列表⻚的 “查看全⽂” , 能进⼊博客详情⻚, 但是这个博客详情⻚是写死的内容. 我们期望能够根据当前的 博客 id 从服务器动态获取博客内容.

约定前后端交互接口

[请求] /blog/getBlogDetail?blogId=1
[响应] {
“code”: 200,
“msg”: “”,
“data”: {
“id”: 1,
“title”: “第⼀篇博客”,
“content”: “111我是博客正⽂我是博客正⽂我是博客正⽂”,
“userId”: 1,
“deleteFlag”: 0,
“createTime”: “2023-10-21 16:56:57”,
“updateTime”: “2023-10-21T08:56:57.000+00:00”
}
}

实现服务器代码
@RequestMapping("/getBlogDetail")
public Blog getBlogDeatail(Integer blogId){return blogService.getBlogDeatil(blogId);
}
public BlogInfo getBlogDetail(Integer blogId) {return blogInfoMapper.queryById(blogId);}

实现登录

分析
传统思路:
• 登陆⻚⾯把⽤⼾名密码提交给服务器.
• 服务器端验证⽤⼾名密码是否正确, 并返回校验结果给后端
• 如果密码正确, 则在服务器端创建 Session . 通过 Cookie 把 sessionId 返回给浏览器.
问题
集群环境下⽆法直接使⽤Session.

解决方案

使用令牌

令牌其实就是⼀个⽤⼾⾝份的标识, 名称起的很⾼⼤上, 其实本质就是⼀个字符串.

服务器具备⽣成令牌和验证令牌的能⼒我们使⽤令牌技术, 继续思考上述场景:

  1. ⽤⼾登录 ⽤⼾登录请求, 经过负载均衡, 把请求转给了第⼀台服务器, 第⼀台服务器进⾏账号密码验证, 验证成功后, ⽣成⼀个令牌, 并返回给客⼾端.
  2. 客⼾端收到令牌之后, 把令牌存储起来. 可以存储在Cookie中, 也可以存储在其他的存储空间(⽐如localStorage)
  3. 查询操作 ⽤⼾登录成功之后, 携带令牌继续执⾏查询操作, ⽐如查询博客列表. 此时请求转发到了第⼆台机器, 第⼆台机器会先进⾏权限验证操作. 服务器验证令牌是否有效, 如果有效, 就说明⽤⼾已经执⾏了登录操作, 如果令牌是⽆效的, 就说明⽤⼾之前未执⾏登录操作.
JWT令牌

令牌本质就是⼀个字符串, 他的实现⽅式有很多, 我们采⽤⼀个JWT令牌来实现
介绍
JWT全称: JSON Web Token
官⽹: https://jwt.io/
JSON Web Token(JWT)是⼀个开放的⾏业标准(RFC 7519), ⽤于客⼾端和服务器之间传递安全可靠的信息.
其本质是⼀个token, 是⼀种紧凑的URL安全⽅法.

jwt组成

Header(头部) 头部包括令牌的类型(即JWT)及使⽤的哈希算法(如HMAC SHA256或RSA)
Payload(负载) 负载部分是存放有效信息的地⽅, ⾥⾯是⼀些⾃定义内容. ⽐如:{“userId”:“123”,“userName”:“zhangsan”} , 也可以存在jwt提供的现场字段, ⽐如exp(过期时间戳)等.
Signature(签名) 此部分⽤于防⽌jwt内容被篡改, 确保安全性防⽌被篡改, ⽽不是防⽌被解析.JWT之所以安全, 就是因为最后的签名. jwt当中任何⼀个字符被篡改, 整个令牌都会校验失败.就好⽐我们的⾝份证, 之所以能标识⼀个⼈的⾝份, 是因为他不能被篡改, ⽽不是因为内容加密.(任何⼈都可以看到⾝份证的信息, jwt 也是)

JWT令牌生成和校验
  1. 引⼊JWT令牌的依赖
<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> 
  1. 使⽤Jar包中提供的API来完成JWT令牌的⽣成和校验
@Slf4j
public class JwtUtils {//三十分钟过期时间public static final long expiration = 30 * 60 * 1000;public static final String secretString = "dVnsmy+SIX6pNptQdeclDSJ26EMSPEIhvZYKBTTug4k=";public 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){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");}
}
实现用户登录

学习令牌的使⽤之后, 接下来我们通过令牌来完成⽤⼾的登录

  1. 登陆⻚⾯把⽤⼾名密码提交给服务器.
  2. 服务器端验证⽤⼾名密码是否正确, 如果正确, 服务器⽣成令牌, 下发给客⼾端.
  3. 客⼾端把令牌存储起来(⽐如Cookie, local storage等), 后续请求时, 把token发给服务器
  4. 服务器对令牌进⾏校验, 如果令牌正确, 进⾏下⼀步操作
约定前后端交互接⼝

[请求]/user/login username=test&password=123
[响应]{
“code”: 200,
“msg”: “”,
“data”: "eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJ6aGFuZ3NhbiIsImlhdC
}
//验证成功, 返回token, 验证失败返回 “”

实现服务器代码

创建UserController

@RequestMapping("/login")public Result login(String userName, String password){/*** 参数校验* 密码校验* 生成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("密码错误!");
//        }boolean isOk = SecurityUtils.verify(password,userInfo.getPassword());if(!isOk){return Result.fail("密码错误!");}//生成tokenMap<String,Object> claim = new HashMap<>();claim.put("id",userInfo.getId());claim.put("name",userInfo.getUserName());String token = JwtUtils.genToken(claim);return Result.success(token);}

实现强制要求登录

当⽤⼾访问 博客列表⻚ 和 博客详情⻚ 时, 如果⽤⼾当前尚未登陆, 就⾃动跳转到登陆⻚⾯.我们可以采⽤拦截器来完成, token通常由前端放在header中, 我们从header中获取token, 并校验token是否合法

添加拦截器
@Slf4j
@Configuration
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//此处进行用户强制登陆校验//1.从header中获取token//验证token(约定前端发送请求时 header之中有key为user_token 值为token的键值对)String token = request.getHeader("user_token");log.info("从header中获取token,token:{}",token);Claims claims = JwtUtils.parseToken(token);if(claims == null){//token是不合法的response.setStatus(401);return false;}return true;}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {public static final List<String> excludepath = Arrays.asList("/user/login","/**/*.html","/pic/**","/js/**","/css/**","/blog-editormd/**");@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns(excludepath);}
}

实现显示用户信息

约定前后端交互接口

在博客列表页 获取当前文章作者的用户信息

请求]
/user/getUserInfo
[响应]
{
userId: 1,
username: test

}

在博客详情页 获取当前文章作者的用户信息

[请求]
/user/getAuthorInfo?blogId=1
[响应]
{
userId: 1,
username: test
}

实现服务器代码

在UserController添加代码

//获取登录用户的信息@RequestMapping("/getUserInfo")public UserInfo userInfo(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);}

UserService

 public UserInfo getAuthorInfo(Integer blogId) {//根据blogId获取userId//根据userId获取userInfoBlogInfo blogInfo = blogInfoMapper.queryById(blogId);if(blogInfo == null){return null;}return userInfoMapper.queryById(blogInfo.getUserId());}

实现发布博客

约定前后端交互接口

[请求]
/blog/add
title=标题&content=正⽂…
[响应]
{
“code”: 200,
“msg”: “”,
“data”: true
}
//true 成功
//false 失败

实现服务器代码

修改BlogController代码

@RequestMapping("/add")public Result publishBlog(String title, String content, HttpServletRequest request) {//从token中获取userIdString token = request.getHeader("user_token");Integer userId = JwtUtils.getUserIdFromToken(token);if (userId == null || userId < 0) {return Result.fail("用户未登录", false);}//插入博客BlogInfo blogInfo = new BlogInfo();blogInfo.setUserId(userId);blogInfo.setTitle(title);blogInfo.setContent(content);blogService.insertBlog(blogInfo);return Result.success(true);}

实现删除/编辑博客

进⼊⽤⼾详情⻚时, 如果当前登陆⽤⼾正是⽂章作者, 则在导航栏中显⽰ [编辑] [删除] 按钮, ⽤⼾点击时则进⾏相应处理.

需要实现两件事:

• 判定当前博客详情⻚中是否要显⽰[编辑] [删除] 按钮
• 实现编辑/删除逻辑.

删除采⽤逻辑删除, 所以和编辑其实为同⼀个接⼝.

约定前后端交互接口
  1. 判定是否要显⽰[编辑] [删除] 按钮
    修改之前的 获取博客 信息的接⼝, 在响应中加上⼀个字段.
    获取博客详情

请求]
/blog/getBlogDetail?blogId=1
[响应]
{
“code”: 200,
“msg”: “”,
“data”: {
“id”: 1,
“title”: “第⼀篇博客”,
“content”: “111我是博客正⽂我是博客正⽂我是博客正⽂”,
“userId”: 1,
“loginUser”: 1
“deleteFlag”: 0,
“createTime”: “2023-10-21 16:56:57”,
“updateTime”: “2023-10-21T08:56:57.000+00:00”
}
}

修改博客

[请求]
/blog/update
[参数]
Content-Type: application/json
{
“title”: “测试修改⽂章”,
“content”: “在这⾥写下⼀篇博客”,
“blogId”: “4”
}
[响应]
{
“code”: 200,
“msg”: “”,
“data”: true
}

删除博客

[请求]
/blog/delete?blogId=1
[响应]
{
“code”: 200,
“msg”: “”,
“data”: true
}

实现服务器代码

给blog类新增一个字段

@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 isLoginUser;public String getCreateTime() {return DateUtils.formatDate(createTime);}
}

修改BlogControl

//获取博客详情@RequestMapping("/getBlogDetail")public BlogInfo getBlogDetail(Integer blogId, HttpServletRequest request) {BlogInfo blogInfo = blogService.getBlogDetail(blogId);//判断作者是否是登录用户String token = request.getHeader("user_token");Integer userId = JwtUtils.getUserIdFromToken(token);if (userId != null && userId == blogInfo.getUserId()) {blogInfo.setLoginUser(true);}return blogInfo;}

添加update/delete方法

/*** 编辑博客*/@RequestMapping("/update")public boolean updateBlog(Integer id, String title, String content){BlogInfo blogInfo = new BlogInfo();blogInfo.setTitle(title);blogInfo.setContent(content);//根据博客ID去更新数据blogInfo.setId(id);blogService.updateBlog(blogInfo);return true;}/*** 删除博客*/@RequestMapping("/delete")public boolean deleteBlog(Integer blogId){blogService.delete(blogId);return true;}

加密/加盐

加密介绍
在MySQL数据库中, 我们常常需要对密码, ⾝份证号, ⼿机号等敏感信息进⾏加密, 以保证数据的安全性.如果使⽤明⽂存储, 当⿊客⼊侵了数据库时, 就可以轻松获取到⽤⼾的相关信息, 从⽽对⽤⼾或者企业造成信息泄漏或者财产损失.⽬前我们⽤⼾的密码还是明⽂设置的, 为了保护⽤⼾的密码信息, 我们需要对密码进⾏加密
密码算法分类
密码算法主要分为三类: 对称密码算法, ⾮对称密码算法, 摘要算法

  1. 对称密码算法 是指加密秘钥和解密秘钥相同的密码算法. 常⻅的对称密码算法有: AES, DES, 3DES, RC4, RC5, RC6 等.
  2. ⾮对称密码算法 是指加密秘钥和解密秘钥不同的密码算法. 该算法使⽤⼀个秘钥进⾏加密, ⽤另外⼀ 个秘钥进⾏解密. ◦ 加密秘钥可以公开,⼜称为 公钥 ◦ 解密秘钥必须保密,⼜称为 私钥 常⻅的⾮对称密码算法有:RSA,DSA,ECDSA, ECC 等
  3. 摘要算法 是指把任意⻓度的输⼊消息数据转化为固定⻓度的输出数据的⼀种密码算法. 摘要算法是 不可逆的, 也就是⽆法解密. 通常⽤来检验数据的完整性的重要技术, 即对数据进⾏哈希计算然后⽐ 较摘要值, 判断是否⼀致. 常⻅的摘要算法有: MD5,SHA系列(SHA1, SHA2等), CRC(CRC8, CRC16, CRC32)

我们在博客系统中使用摘要算法中的MD5算法来加密

问题: 虽然经过MD5加密后的密⽂⽆法解密, 但由于相同的密码经过MD5哈希之后的密⽂是相同的, 当存储⽤⼾密码的数据库泄露后, 攻击者会很容易便能找到相同密码的⽤⼾, 从⽽降低了破解密码的难度. 因此, 在对⽤⼾密码进⾏加密时,需要考虑对密码进⾏包装, 即使是相同的密码, 也保存为不同的密⽂. 即使⽤⼾输⼊的是弱密码, 也考虑进⾏增强, 从⽽增加密码被攻破的难度.

解决⽅案: 采⽤为⼀个密码拼接⼀个随机字符来进⾏加密, 这个随机字符我们称之为"盐". 假如有⼀个加盐后的加密串,⿊客通过⼀定⼿段这个加密串, 他拿到的明⽂并不是我们加密前的字符串, ⽽是加密前的字符串和盐组合的字符串, 这样相对来说⼜增加了字符串的安全性.
在这里插入图片描述

解密流程: MD5是不可逆的, 通常采⽤"判断哈希值是否⼀致"来判断密码是否正确.如果⽤⼾输⼊的密码, 和盐值⼀起拼接后的字符串经过加密算法, 得到的密⽂相同, 我们就认为密码正确(密⽂相同, 盐值相同, 推测明⽂相同)
在这里插入图片描述

写加密/解密工具类
@Slf4j
public class SecurityUtils {/*** 加密* @param password 明文密码* @return 盐值+密文*/public static String encry(String password){//生成随机盐值String salt = UUID.randomUUID().toString().replace("-","");System.out.println(salt);//加密 盐值+明文String securityPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());//数据库中存储 盐值+明文return salt+securityPassword;}/*** 校验* @param inputPassword 输入的密码* @param sqlPassword 数据库中取到的密码* @return 密码是否正确*/public static boolean verify(String inputPassword, String sqlPassword){//取出盐值if(sqlPassword == null || sqlPassword.length() != 64){return false;}String salt = sqlPassword.substring(0,32);//得到密文String securityPassword = DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes());log.info(salt + securityPassword);return (salt + securityPassword).equals(sqlPassword);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/8914.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Salesforce Flow - Screen Flow设置详解

今天给大家来详细介绍下如何设置Screen Flow&#xff1a; 一、Screen Flow元素介绍 先介绍下Screen Flow&#xff08;屏幕流&#xff09;的界面及元素&#xff0c;如下图Screen Flow的设计看板包含有元素、字段及界面看板&#xff1b;Component中包含有各种类型的字段可以通过…

读天才与算法:人脑与AI的数学思维笔记22_中文房间

1. 华生的工作模式 1.1. 请你想象一个巨大的场景&#xff0c;其中有单词、名字和其他可能的答案&#xff0c;它们散布在各处 1.1.1. IBM所做的第一步是以某种连贯的方式排列单词 1.1.2. 第二步是理解每个问题&#xff0c;并为该问题生成候选位置标记 1.1.2.1. 爱因斯坦会演…

IT项目管理-大题【太原理工大学】

一、根据进度网络写出时间参数表、关键路径、总工期 此类题一般是给一个表&#xff0c;问三问。 第一问会问某个活动的时间参数&#xff0c;但我们需要把整个表都求出来&#xff0c;否则单求一个很困难&#xff08;如果你就是不想求整张表也行&#xff0c;不是硬性要求&#xf…

vue3 - 150

目录 vue优势使用方式编写vue代码指令响应式数据其他 vue优势 功能全面生态好&#xff0c;语法简洁效率高&#xff0c;免去 DOM 操作苦&#xff0c;开发重任一肩挑&#xff01; 使用方式 1.通过cdn引入来将 Vue 应用到整个页面 2.或通过官方脚手架 create-vue来创建完整的v…

直播报名 | 珈和科技携手潍柴雷沃共探“现代农场”未来式

数据赋农季系列直播第四期&#xff0c;我们将以“未来农业发展趋势之农场智慧化、管理数据化”为主题展开&#xff0c;此次系列直播由珈和科技及湖北珞珈实验室共同主办&#xff0c;第四期直播很荣幸邀请到潍柴雷沃参与其中&#xff0c;双方将就智慧农服平台和数据交易SaaS平台…

基于PHP高考志愿填报系统搭建私有化部署源码

金秋志愿高考志愿填报系统是一款为高中毕业生提供志愿填报服务的在线平台。该系统旨在帮助学生更加科学、合理地选择自己的大学专业和学校&#xff0c;从而为未来的职业发展打下坚实的基础。 该系统的主要功能包括:报考信息查询、志愿填报数据指导、专业信息查询、院校信息查询…

Python-VBA函数之旅-round函数

目录 一、round函数的常见应用场景 二、round函数使用注意事项 三、如何用好round函数&#xff1f; 1、round函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://blog.csdn.net/ygb_1024?spm1010.2…

04、Kafka集群安装

1、准备工作 首先准备一台虚拟机&#xff0c;centos7系统&#xff0c;先在一台上配置安装后&#xff0c;最后克隆成多台机器。 1.1 安装JDK &#xff08;1&#xff09;下载JDK&#xff0c;上传到 /root/software 路径 下载地址&#xff1a;https://www.oracle.com/cn/java/…

【PyTorch实战演练】使用CelebA数据集训练DCGAN(深度卷积生成对抗网络)并生成人脸(附完整代码)

文章目录 0. 前言1. CelebA数据集1.1 核心特性与规模1.2 应用与用途1.3 获取方式1.4 数据预处理 2. DCGAN的模型构建2.1 生成器模型2.2 判别器模型 3. DCGAN的模型训练&#xff08;重点&#xff09;3.1 训练参数3.2 模型参数初始化3.3 训练过程 4. 结果展示4.1 loss值变化过程4…

Linux —— 进程间通信

目录 一、进程间通信的介绍二、管道三、匿名管道四、命名管道五、system V进程间通信 一、进程间通信的介绍 1.进程间通信的概念 进程通信&#xff08;Interprocess communication&#xff09;&#xff0c;简称&#xff1a;IPC&#xff1b; 本来进程之间是相互独立的。但是…

Elasticsearch的基本使用

Elasticsearch的基本使用 1.基本概念1.1 文档和字段1.2 索引和映射1.3 mysql与elasticsearch对比 2.索引库2.1 es中mapping映射属性2.2.es中索引库的增删改查 3.文档3.1 新增文档3.2 查询文档3.3 删除文档3.4 修改文档3.4.1 全量修改3.4.2 增量修改3.5 总结 4.DSL查询语法4.1 D…

【LLM第三篇】名词解释:RLHF——chatgpt的功臣

RLHF (Reinforcement Learning from Human Feedback) &#xff0c;直译为&#xff1a;“来自人类反馈的强化学习”。RLHF是一种结合了强化学习和人类反馈的机器学习方法&#xff0c;主要用于训练大模型以执行复杂的任务&#xff0c;尤其是当这些任务难以通过传统的奖励函数来精…

CCF-Csp算法能力认证, 202303-1重复局面(C++)含解析

前言 推荐书目&#xff0c;在这里推荐那一本《算法笔记》&#xff08;胡明&#xff09;&#xff0c;需要PDF的话&#xff0c;链接如下 「链接&#xff1a;https://pan.xunlei.com/s/VNvz4BUFYqnx8kJ4BI4v1ywPA1?pwd6vdq# 提取码&#xff1a;6vdq”复制这段内容后打开手机迅雷…

大语言模型LLM入门篇

大模型席卷全球&#xff0c;彷佛得模型者得天下。对于IT行业来说&#xff0c;以后可能没有各种软件了&#xff0c;只有各种各样的智体&#xff08;Agent&#xff09;调用各种各样的API。在这种大势下&#xff0c;笔者也阅读了很多大模型相关的资料&#xff0c;和很多新手一样&a…

深圳CPDA|如何利用数据分析改进业务流程,提高效率?

在当今数字化时代&#xff0c;数据已经成为企业决策和优化的关键资源。通过有效地收集、分析和应用数据&#xff0c;企业可以深入了解其业务流程中的瓶颈和问题&#xff0c;从而改进流程&#xff0c;提高效率。本文将探讨如何利用数据分析改进业务流程&#xff0c;并提高效率。…

Vue3+vite优化基础架构(3)--- 优化vue-i18n国际化配置

Vue3vite优化基础架构&#xff08;3&#xff09;--- 优化vue-i18n国际化配置 说明全部页面进行中英文使用测试中英文切换对ElementPlus里面的所有组件进行中英文切换 说明 这里记录下自己在Vue3vite的项目增加全局中英文切换按钮对页面进行中英文切换及同时对ElementPlus里面的…

练习题(2024/5/9)

1删除二叉搜索树中的节点 给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除节点可分为…

融知财经:期货在哪里可以交易?期货交易有哪些交易规则?

作为当前金融市场的一种投资方式&#xff0c;期货只适合一些投资者&#xff0c;比如想获得高收益的投资者&#xff0c;因为期货的风险系数很高。但是很多投资者还不知道期货的意思&#xff0c;在一个固定的交易场所&#xff0c;期货是买卖标准化商品或金融资产的远期合约的交易…

RK3568 学习笔记 : u-boot 下通过设置 env ethact 设置当前工作的以太网设备

前言 正点原子 &#xff1a;RK3568 开发板 atompi-ca1 默认有两个网口&#xff0c;通过 u-boot mii 命令&#xff0c;可以查看 网口信息 > mii device MII devices: ethernetfe010000 ethernetfe2a0000 Current device: ethernetfe010000u-boot 下的以太网&#xff0c;不同…