实战--8

一、@JsonSerialize 解决前端 Long 雪花 ID 精度丢失 💡

1. 问题根源

雪花算法生成Long(64位)分布式 ID,JS Number 最大安全整数:2^53 ≈ 9007199254740991;当雪花 ID 数值超过该上限,前端 JSON 接收 Number 类型时自动丢失低位精度,ID 错乱查不到数据。

2. 解决方案

java

运行

复制代码
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
  • 作用:Jackson 序列化 JSON 时,强制把 Long 字段转为字符串返回前端,前端以 String 接收,彻底规避 JS 数值溢出丢失精度;
  • 拓展:全局配置 Jackson 转换器,批量统一所有 Long 字段序列化策略,不用逐个字段加注解。

二、评论模块:无限层级树形评论实现(父子评论分级)

1. 数据库层级设计

表格

level 含义 parentId 规则
1 一级根评论 parentId=0
2 子评论(回复根评论 / 回复他人) parentId = 父评论主键 ID
  • articleId:绑定所属文章 ID;toUid:回复目标用户 ID;
  • 新增评论时:根评论 level=1,非根评论 level=2。

2. 发布评论 comment () 全逻辑

java

运行

复制代码
@Override
public Result comment(CommentParam commentParam) {
    //1. 从ThreadLocal获取当前登录用户
    SysUser sysUser = UserThreadLocal.get();
    Comment comment = new Comment();
    comment.setArticleId(commentParam.getArticleId());
    comment.setAuthorId(sysUser.getId());
    comment.setContent(commentParam.getContent());
    comment.setCreateDate(System.currentTimeMillis());

    //2. 判断层级:parent为空/0 → 一级评论level=1,否则子评论level=2
    Long parent = commentParam.getParent();
    if(parent == null || parent == 0){
        comment.setLevel(1);
    }else{
        comment.setLevel(2);
    }
    //三元赋值parentId,空默认0
    comment.setParentId(parent == null ? 0 : parent);
    //回复目标用户ID,无则赋值0
    Long toUserid = commentParam.getToUserId();
    comment.setToUid(toUserid == null ? 0 : toUserid);

    //3. 插入评论库
    commentMapper.insert(comment);
    //4. 文章评论数自增:comment_counts = comment_counts +1(原生SQL自增,原子操作防并发)
    UpdateWrapper<Article> updateWrapper = Wappers.update();
    updateWrapper.eq("id",comment.getArticleId())
                 .setSql("comment_counts=comment_counts+1");
    articleMapper.update(null,updateWrapper);
    return Result.success(null);
}

⚠️setSql("comment_counts=comment_counts+1")优势:SQL 层面原子自增,多线程并发评论不会出现计数少加。

3. 查询文章评论:递归组装树形 VO

3.1 步骤拆解
  1. 先查level=1 的一级根评论集合
  2. 遍历每一条根评论,递归查询parentId=根ID的子评论;
  3. 子评论绑定到父 VO 的childrens字段,形成树形结构返回前端。

java

运行

复制代码
// 顶层查询:只查当前文章一级评论
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Comment::getArticleId,id)
            .eq(Comment::getLevel,1);
List<Comment> comments = commentMapper.selectList(queryWrapper);
List<CommentVo> voList = copyList(comments);

java

运行

复制代码
//单个评论转VO + 递归封装子评论
private CommentVo copy(Comment comment){
    CommentVo vo = new CommentVo();
    BeanUtils.copyProperties(comment,vo);
    //封装评论发布者信息
    UserVo author = sysUserService.findUserById(comment.getAuthorId());
    vo.setAuthor(author);

    //level=1:根据自身ID查子评论;level=2:根据toUid查子评论
    Integer level = comment.getLevel();
    List<CommentVo> childList;
    if(level == 1){
        childList = findCommentsByParentId(comment.getId());
    }else{
        childList = findCommentsByParentId(comment.getToUid());
    }
    vo.setChildrens(childList);
    return vo;
}
//根据父ID查询所有子评论
private List<CommentVo> findCommentsByParentId(long pid){
    LambdaQueryWrapper<Comment> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(Comment::getParentId,pid);
    List<Comment> childComments = commentMapper.selectList(wrapper);
    return copyList(childComments);
}

4. 优缺点与优化方案

✅ 优点:代码简洁,无限层级评论通用;❌ 缺点:N+1 查询问题,1 次查一级评论 + N 次循环查子评论,数据量大 DB 查询频繁;👉 优化:

  1. 一次性查出当前文章全量评论,内存中遍历组装树形,减少 DBIO;
  2. 热门文章用 Redis 缓存评论树,减轻 DB 压力。

三、配套复用知识点回顾

  1. ThreadLocal :发布评论从UserThreadLocal.get()拿登录用户,不用前端传作者 ID,防篡改;拦截器鉴权成功存入、请求结束 remove 防内存泄漏;
  2. MyBatis-Plus 原生 SQL 自增setSql实现字段原子 + 1,替代 Java 先查后改,规避并发超量。
相关推荐
前端不太难17 小时前
鸿蒙 App 智能助手:实现原理 + 开发实践
华为·状态模式·harmonyos
知识汲取者1 天前
每日一篇高频面试题之 Redis 持久化
状态模式
前端不太难2 天前
鸿蒙游戏需要 GameEngine 吗?
游戏·状态模式·harmonyos
薛定猫AI2 天前
【深度解析】大模型编码能力评测:Reasoning Effort、Agentic Workflow 与多模型 API 实战
状态模式
木斯佳3 天前
前端八股文面经大全:字节跳动-存储部门一面(2026-05-29)·面经深度解析
前端·状态模式
C+++Python3 天前
线性状态和状态模式有什么区别?
状态模式
边界条件╝3 天前
微前端进阶(四)
前端·状态模式
我是一颗柠檬3 天前
【Java后端技术亮点】动态路由权限(按钮级权限),细粒度控制到按钮级别
java·开发语言·后端·状态模式
霸道流氓气质4 天前
Excel 数据导出实战指南
excel·状态模式