博客系统小笔记

BeanUtils(BeanUtils工具)

1.拷贝属性

BeanUtils.copyProperties(类1, 类2)

是 Apache Commons BeanUtils 库中的一个常用方法,用于实现两个 JavaBean 对象之间的属性拷贝。

作用说明:

  • 该方法会将第一个参数(源对象 blogInfo)的属性值,复制到第二个参数(目标对象 blogInfoResponse)的同名属性中。
  • 要求源对象和目标对象拥有相同名称的属性(且属性的 getter/setter 方法符合 JavaBean 规范),数据类型需兼容。

QueryWrapper构造查询条件

QueryWrapperMyBatis-Plus 提供的强大的查询条件构造器,它能以编程的方式灵活构建 SQL 查询条件,避免手动拼接 SQL 字符串带来的安全问题(如 SQL 注入)和代码可读性问题。

复制代码
// 构建查询条件
QueryWrapper<BlogInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(BlogInfo::getDeleteFlag, 0);

// 执行查询
List<BlogInfo> blogList = blogInfoMapper.selectList(queryWrapper);

这行代码是 MyBatis-Plus 框架中用于构建查询条件的典型用法,具体含义如下:

代码解析:

  • queryWrapper:是 MyBatis-Plus 提供的查询条件构造器对象,用于动态 SQL 查询条件
  • .lambda():切换到 Lambda 表达式写法,支持通过类的方法引用来指定属性,避免硬编码字段名
  • .eq(BlogInfo::getDeleteFlag, 0):添加一个 "等于" 条件,等价于 SQL 中的 WHERE delete_flag = 0
    • BlogInfo::getDeleteFlag:通过 Lambda 方法引用指定实体类 BlogInfodeleteFlag 属性
    • 0:表示要匹配的值(通常 0 表示未删除,1 表示已删除,用于逻辑删除场景)

作用:

这行代码的最终效果是在查询中添加条件:筛选出 deleteFlag 字段值为 0 的 BlogInfo 记录,通常用于查询未被逻辑删除的数据。

List<BlogInfoResponse> list=blogInfos.stream().map()

代码解析:

  1. blogInfos.stream():将集合转换为 Stream 流,开启流式处理
  2. .map(...):对流中的每个 BlogInfo 对象进行转换操作
    • 接收一个 lambda 表达式作为参数,定义如何将 BlogInfo 转换为 BlogInfoResponse
  3. .collect(Collectors.toList()):将转换后的流收集为 List<BlogInfoResponse>

Claims

MD5(加密/解密)

加密

复制代码
 /**
     * 加密
     * MD5(salt+明文)
     * return:盐值+MD5(盐值+明文)
     */                               //明文(密码)
    public static String encrypt(String password){
        String salt= UUID.randomUUID().toString().replace("-","");//盐值
        String securityPassword= DigestUtils.md5DigestAsHex((salt+password).getBytes(StandardCharsets.UTF_8));//盐值+明文
        return salt+securityPassword;//盐值+(盐值+明文)
    }

生成盐值(Salt)

复制代码
  String salt = UUID.randomUUID().toString().replace("-", "");
  • UUID.randomUUID() 生成一个全局唯一的 UUID(如 e5c8a7b0-1d3f-4b9c-87a2-567890abcdef)。
  • replace("-", "") 去掉 UUID 中的短横线,得到纯字母数字的盐值(如 e5c8a7b01d3f4b9c87a2567890abcdef)。
  • 盐值的作用:每个密码的盐值都不同,即使两个用户密码相同,最终的加密结果也会不同,能抵御 "彩虹表" 攻击。

MD5 哈希加密

复制代码
  String securityPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8));
  • 将 "盐值 + 原始密码" 拼接后,转换为 UTF-8 字节数组。
  • DigestUtils.md5DigestAsHex(Spring 提供的工具类)对字节数组做 MD5 哈希,得到 32 位十六进制的哈希值。
  • MD5 是不可逆哈希算法,加密后无法从结果反推原始密码。

返回加密结果

复制代码
  return salt + securityPassword;
  • 将 "盐值" 和 "MD5 哈希结果" 拼接后返回。这样在验证密码时,可从结果中提取盐值,再用同样的逻辑验证。

解密

复制代码
 /**
     * 验证
     * 数据库中存储的是,盐值+MD5(盐值+明文)
     */                          //明文(密码)                       盐值+(盐值+明文)     
    public static boolean verify(String inputPassword,String sqlPassword){
        if(!StringUtils.hasLength(inputPassword)){//是否为空或空白字符串。
            return false;
        }
        if(sqlPassword==null&&sqlPassword.length()!=64){
            return false;
        }
        String salt=sqlPassword.substring(0,32);//取盐值
        String securityPassword=DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes(StandardCharsets.UTF_8));//盐值+明文
        return sqlPassword.equals(salt+securityPassword);
    }
1. 参数校验
复制代码
if(!StringUtils.hasLength(inputPassword)){
    return false;
}
  • 作用:检查用户输入的密码(inputPassword)是否为空或空白字符串。
  • 逻辑:如果输入密码为空,直接返回 false(验证失败),避免后续无效计算。
2. 验证数据库存储的加密密码格式
复制代码
if(sqlPassword==null&&sqlPassword.length()!=64){
    return false;
}
  • 作用:检查数据库中存储的加密密码(sqlPassword)是否符合预期格式。
  • 预期格式:根据加密方法,sqlPassword 应为 32 位盐值 + 32 位 MD5 哈希值,总长度必须是 64 位。
3. 提取盐值
复制代码
String salt=sqlPassword.substring(0,32);
  • 作用:从数据库存储的加密密码中提取盐值(与加密时的逻辑对应)。
  • 逻辑:加密时盐值是 32 位(UUID 去掉短横线后长度),因此截取前 32 位作为盐值。
4. 重新计算加密结果
复制代码
String securityPassword=DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes(StandardCharsets.UTF_8));
  • 作用:用提取的盐值 + 用户输入的明文密码,重新计算 MD5 哈希值。
  • 逻辑:与加密时的算法完全一致(盐值拼接明文后做 MD5 哈希),确保验证的一致性。
5. 对比验证
复制代码
return sqlPassword.equals(salt+securityPassword);
  • 作用:将重新计算的 "盐值 + 哈希值" 与数据库中存储的 sqlPassword 对比。
  • 逻辑:如果两者相同,说明输入密码正确(返回 true);否则验证失败(返回 false)。

JwtUtils

1.引入依赖

2. 核心属性与初始化

复制代码
// JWT 签名密钥(Base64 编码后的字符串)
private static String SECRET_STRING = "dVnsmy+SIX6pNptQdecLDSJ26EMSPEInvZYKBTtug4k=";
// 签名密钥(用于生成和验证 JWT 的签名)
private static Key key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(SECRET_STRING));
  • SECRET_STRING:是一个 Base64 编码的字符串,作为 JWT 的签名密钥。签名的作用是保证 JWT 在传输过程中不被篡改。
  • key:通过 Keys.hmacShaKeyFor 方法,将 Base64 解码后的密钥转换为 Key 对象,用于 JWT 的签名和验签。这里使用的是 HMAC-SHA 算法(一种对称加密签名算法,生成和验签用同一个密钥)。

3. 生成 JWT(genToken 方法)

复制代码
public static String genToken(Map<String, Object> claims) {
    String compact = Jwts.builder()
            .setClaims(claims)   // 设置 JWT 的 payload(自定义声明,如用户 ID、角色等)
            .signWith(key)       // 使用密钥进行签名
            .compact();          // 压缩生成最终的 JWT 字符串
    return compact;
}
  • 作用:根据传入的 claims(自定义声明,是一个 Map,可以包含用户 ID、用户名、角色等信息)生成 JWT。
  • 流程:
    1. Jwts.builder():创建 JWT 构建器。
    2. setClaims(claims):将自定义声明放入 JWT 的 payload 部分。
    3. signWith(key):使用之前初始化的 key 对 JWT 进行签名,确保完整性和真实性。
    4. compact():将 JWT 各部分(头、payload、签名)压缩成一个字符串返回。

4. 解析 JWT(parseToken 方法)

复制代码
public static Claims parseToken(String token) {
    JwtParser builder = Jwts.parserBuilder()
            .setSigningKey(key)  // 设置验签密钥(必须与生成时的密钥一致)
            .build();
    Claims claims = null;
    try {
        // 解析 JWT 并获取 payload 中的 claims
        claims = builder.parseClaimsJws(token).getBody();
    } catch (Exception e) {
        log.error("token 解析失败,token:" + token);
    }
    return claims;
}
  • 作用:解析传入的 JWT 字符串,验证其签名并提取 payload 中的声明(Claims)。
  • 流程:
    1. Jwts.parserBuilder():创建 JWT 解析器构建器。
    2. setSigningKey(key):设置验签密钥(必须与生成 JWT 时的密钥一致,否则验签失败)。
    3. build():构建解析器。
    4. parseClaimsJws(token):解析 JWT,验证签名。如果签名不匹配或 JWT 格式错误,会抛出异常。
    5. getBody():获取 JWT payload 中的 Claims(即生成时传入的自定义声明)。
    6. 异常处理:如果解析失败(如签名无效、JWT 过期等),捕获异常并记录日志,返回 null

前端

1.储存、跳转

复制代码
$.ajax({
            type:"post",
            url:"/user/login",
            contentType:"application/json",//指定请求体数据的格式为 JSON
            data:JSON.stringify({// JSON.stringify:JavaScript 对象转换为 JSON 字符串
                userName:$("#username").val(),//选中页面中 id="username" 的元素
                password:$("#password").val()//,val()接收用户名
            }),
            success: function(result){
                if(result!=null&&result.code=="SUCCESS"){
                    localStorage.setItem("loginUserId",result.data.userId);
                    localStorage.setItem("userToken",result.data.token);
                    location.href="blog_list.html";
                }else{
                    alert(result.errMsg);
                }
            }
           });

1. 存储用户信息到本地存储:localStorage.setItem(...)

  • localStorage.setItem("loginUserId", result.data.userId)

    • 将用户 ID(result.data.userId,后端返回的用户唯一标识)存储到浏览器的 localStorage 中。
    • localStorage 是浏览器提供的本地存储机制,数据会长期保存在浏览器中(除非手动清除或代码删除)。
  • localStorage.setItem("userToken", result.data.token)

    • 将后端返回的身份令牌(token,用于后续请求的身份验证,如 JWT)存储到 localStorage 中。
    • 后续请求(如查询博客列表、编辑博客)时,前端会从 localStorage 中取出 token,放在请求头(如 Authorization 头)中发送给后端,后端验证 token 以确认用户身份。

2.:将当前页面跳转到 blog_list.html(博客列表页面)。

    • location.href="blog_list.html";
    • 通常登录成功后,会跳转到用户的 "个人中心" 或 "资源列表页",这里是跳转到博客列表页,符合博客系统的业务逻辑。

2.拼接、插入

复制代码
 $.ajax({
        type:"get",
        url:"/blog/getList",
        success:function(result){
            if(result.code=='SUCCESS'&&result.data!=null&&result.data.length>0){
                var finalHtml="";
                for(var blogInfo of result.data){
                    finalHtml+='<div class="blog">';
                    finalHtml+='<div class="title">'+blogInfo.title+'</div>';
                    finalHtml+='<div class="date">'+blogInfo.createTime+'</div>';
                    finalHtml+='<div class="desc">'+blogInfo.content+'</div>';
                    finalHtml+='<a class="detail" href="blog_detail.html?blogId='+blogInfo.id+'">查看全文&gt;&gt;</a>';
                    finalHtml+='</div>';
                }
                $(".container .right").html(finalHtml);
            }
        }
    });

1.拼接博客列表 HTML

复制代码
var finalHtml="";
for(var blogInfo of result.data){
    finalHtml+='<div class="blog">';
    finalHtml+='<div class="title">'+blogInfo.title+'</div>';
    finalHtml+='<div class="date">'+blogInfo.createTime+'</div>';
    finalHtml+='<div class="desc">'+blogInfo.content+'</div>';
    finalHtml+='<a class="detail" href="blog_detail.html?blogId='+blogInfo.id+'">查看全文&gt;&gt;</a>';
    finalHtml+='</div>';
}
  • 作用:遍历博客列表数据,拼接成 HTML 字符串。
    • for(var blogInfo of result.data):循环遍历后端返回的博客数组(result.data),blogInfo 是单个博客对象。
    • 拼接的 HTML 结构:
      • 每个博客用 <div class="blog"> 包裹(用于样式控制)。
      • 包含博客标题(blogInfo.title)、创建时间(blogInfo.createTime)、内容(blogInfo.content)。
      • 查看详情链接(a 标签):跳转至 blog_detail.html 并携带 blogId 参数(用于查询该博客的详细信息)。

2. 渲染到页面

复制代码
$(".container .right").html(finalHtml);
  • 作用:将拼接好的 HTML 字符串插入到页面中。
    • $(".container .right"):用 jQuery 选择器定位到页面中 class="container" 下的 class="right" 元素(通常是博客列表的容器)。
    • .html(finalHtml):将容器的内容替换为拼接好的博客列表 HTML,完成页面渲染。

3.获取网站上?后内容(包括?)

方法一:

复制代码
 url:"/user/getUserInfo?userId="+localStorage.getItem("loginUserId"),

loginUserId 这个键名对应的值,来源于用户登录成功后,前端主动存储到 localStorage 中的用户 ID

来源于1中的localStorage.setItem(...)

方法二:

复制代码
url:"/blog/delete"+location.search,

4.

复制代码
$.ajax({
            type:"get",
            url:"/user/getUserInfo?userId="+localStorage.getItem("loginUserId"),
            success:function(result){
                if(result!=null&&result.code=="SUCCESS"&&result.data!=null){
                    var userInfo=result.data;
                    $(".left .card h3").text(userInfo.userName);
                    $(".left .card a").attr("href".userInfo.githubUrl);
                   }
                }
             });
提取并展示用户信息
复制代码
var userInfo = result.data;  // 提取用户信息对象
$(".left .card h3").text(userInfo.userName);  // 显示用户名
$(".left .card a").attr("href", userInfo.githubUrl);  // 设置 GitHub 链接
  • var userInfo = result.data :从响应中提取用户信息对象(result.data 包含后端返回的用户详情,如用户名、GitHub 地址等)。
  • 显示用户名
    • $(".left .card h3"):用 jQuery 选择器定位到页面中 class="left"class="card" 下的 <h3> 标签(通常是展示用户名的位置)。
    • .text(userInfo.userName):将 <h3> 标签的文本内容设置为后端返回的用户名(userInfo.userName)。
  • 设置 GitHub 链接
    • $(".left .card a"):定位到卡片中的 <a> 标签(通常是 GitHub 链接)。
    • .attr("href", userInfo.githubUrl):将链接的 href 属性设置为后端返回的 GitHub 地址(userInfo.githubUrl),点击后可跳转到用户的 GitHub 主页。

5.

复制代码
if(result.code=="SUCCESS"&&result.data!=null){
                        $(".content .title").text(result.data.title);
                         $(".content .date").text(result.data.createTime);
                          $(".content .detail").text(result.data.content);
                     editormd.markdownToHTML("detail", {
                               markdown: result.data.content
                    });

editormd.markdownToHTML("detail", {
    markdown: result.data.content
});
  • 第一个参数 "detail":表示页面中用于展示渲染结果的 DOM 元素的 ID (即 <div id="detail"></div> 这样的容器)。转换后的 HTML 会被插入到这个元素中。

  • 第二个参数 { markdown: result.data.content }:配置对象,markdown 属性指定需要转换的 Markdown 源文本。

    • result.data.content 通常是后端返回的博客内容(假设这是一篇用 Markdown 编写的博客,内容存储为 Markdown 格式的字符串)
相关推荐
程序员大雄学编程3 小时前
「机器学习笔记11」深入浅出:解密基于实例的学习(KNN算法核心原理与实践)
人工智能·笔记·机器学习
代码or搬砖4 小时前
Git学习笔记(二)
笔记·git·学习
报错小能手5 小时前
linux学习笔记(26)计算机网络基础
linux·笔记·学习
hbqjzx5 小时前
带条件的排名问题
笔记
少年、潜行8 小时前
IMX6ULL学习笔记_Boot和裸机篇(6)--- IMX6ULL简单SHELL以及SEGGER ES的Printf和字节对齐问题
笔记·学习·imx6ull·字节对齐·printf格式化
取酒鱼食--【余九】10 小时前
GRU(门控循环单元) 笔记
笔记·深度学习·gru
bnsarocket10 小时前
Verilog和FPGA的自学笔记5——三八译码器(case语句与锁存器)
笔记·fpga开发·verilog·自学
摇滚侠11 小时前
Spring Boot 3零基础教程,自动配置机制,笔记07
spring boot·笔记·后端
聪明的笨猪猪16 小时前
Java Redis “持久化”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试