自定义树工具v2.0+评论回复功能开发

1.新增评论回复

1.EasyCode生成代码
2.SaveShareCommentReplyReq.java
package com.sunxiansheng.circle.api.req;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.util.List;

/**
 * <p>
 * 评论及回复信息
 * </p>
 *
 * @author ChickenWing
 * @since 2024/05/16
 */
@Getter
@Setter
public class SaveShareCommentReplyReq implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 原始动态ID
     */
    private Long momentId;

    /**
     * 回复类型 1评论 2回复
     */
    private Integer replyType;

    /**
     * 评论目标id 评论则是动态ID 回复则是评论内容ID
     */
    private Long targetId;

    /**
     * 内容
     */
    private String content;

    /**
     * 图片内容
     */
    private List<String> picUrlList;

}

3.ShareCommentReplyController.java
package com.sunxiansheng.circle.server.controller;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.base.Preconditions;
import com.sunxiansheng.circle.api.common.Result;
import com.sunxiansheng.circle.api.req.SaveShareCommentReplyReq;
import com.sunxiansheng.circle.server.entity.po.ShareCommentReply;
import com.sunxiansheng.circle.server.entity.po.ShareMoment;
import com.sunxiansheng.circle.server.mapper.ShareMomentMapper;
import com.sunxiansheng.circle.server.service.ShareCommentReplyService;
import com.sunxiansheng.circle.server.entity.page.PageResult;
import com.sunxiansheng.circle.server.service.ShareMomentService;
import com.sunxiansheng.circle.server.util.LoginUtil;
import com.sunxiansheng.practice.api.enums.IsDeleteFlagEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;

/**
 * 评论及回复信息(ShareCommentReply)表控制层
 *
 * @author sun
 * @since 2024-07-16 17:08:33
 */
@RestController
@RequestMapping("/share/comment")
@Slf4j
public class ShareCommentReplyController {
    /**
     * 服务对象
     */
    @Resource
    private ShareCommentReplyService shareCommentReplyService;

    @Resource
    private ShareMomentService shareMomentService;

    /**
     * 发布内容
     */
    @PostMapping(value = "/save")
    public Result<Boolean> save(@RequestBody SaveShareCommentReplyReq req) {
        try {
            if (log.isInfoEnabled()) {
                log.info("发布内容入参{}", JSON.toJSONString(req));
            }
            Preconditions.checkArgument(Objects.nonNull(req), "参数不能为空!");
            Preconditions.checkArgument(Objects.nonNull(req.getReplyType()), "类型不能为空!");
            Preconditions.checkArgument(Objects.nonNull(req.getMomentId()), "内容ID不能为空!");
            // 查询原始动态是否存在
            ShareMoment moment = shareMomentService.queryById(req.getMomentId());
            Preconditions.checkArgument((Objects.nonNull(moment) && moment.getIsDeleted() != IsDeleteFlagEnum.DELETED.getCode()), "非法内容!");
            Preconditions.checkArgument((Objects.nonNull(req.getContent()) || Objects.nonNull(req.getPicUrlList())), "内容不能为空!");
            Boolean result = shareCommentReplyService.saveComment(req);
            if (log.isInfoEnabled()) {
                log.info("发布内容{}", JSON.toJSONString(result));
            }
            return Result.ok(result);
        } catch (IllegalArgumentException e) {
            log.error("参数异常!错误原因{}", e.getMessage(), e);
            return Result.fail(e.getMessage());
        } catch (Exception e) {
            log.error("发布内容异常!错误原因{}", e.getMessage(), e);
            return Result.fail("发布内容异常!");
        }
    }

}

4.ShareCommentReplyService.java
    /**
     * 新增评论/回复
     *
     * @param req
     * @return
     */
    Boolean saveComment(SaveShareCommentReplyReq req);
5.ShareCommentReplyServiceImpl.java
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveComment(SaveShareCommentReplyReq req) {
        // 获取信息
        Long momentId = req.getMomentId();
        Integer replyType = req.getReplyType();
        Long targetId = req.getTargetId();
        String content = req.getContent();
        List<String> picUrlList = req.getPicUrlList();
        // 构建po
        ShareCommentReply shareCommentReply = new ShareCommentReply();
        shareCommentReply.setMomentId(Math.toIntExact(momentId));
        shareCommentReply.setReplyType(replyType);
        // 获取当前用户loginId
        String loginId = LoginUtil.getLoginId();
        // 获取动态创建人,判断是否是作者
        ShareMoment shareMoment = shareMomentMapper.queryById(momentId);
        String shareMomentAuthor = shareMoment.getCreatedBy();
        Integer isAuthor = Objects.nonNull(shareMomentAuthor) && Objects.equals(loginId, shareMomentAuthor) ? 1 : 0;
        // 根据类型决定插入评论信息还是回复信息,1评论,2回复
        if (replyType == 1) {
            // 如果是评论的话,targetId就应该是评论的动态id
            shareCommentReply.setToId(targetId);
            shareCommentReply.setToUser(loginId);
            shareCommentReply.setToUserAuthor(isAuthor);
            // 如果是评论,则父级id就是-1
            shareCommentReply.setParentId(-1L);
        } else {
            // 如果是回复,targetId就应该是回复的评论的id
            shareCommentReply.setReplyId(targetId);
            shareCommentReply.setReplyUser(loginId);
            shareCommentReply.setReplayAuthor(isAuthor);
            // 如果是回复,则父级id就是targetId
            shareCommentReply.setParentId(targetId);
        }
        shareCommentReply.setContent(content);
        if (!CollectionUtils.isEmpty(picUrlList)) {
            shareCommentReply.setPicUrls(JSON.toJSONString(picUrlList));
        }
        shareCommentReply.setCreatedBy(loginId);
        shareCommentReply.setCreatedTime(new Date());
        shareCommentReply.setIsDeleted(0);
        // 动态的回复数+1
        shareMomentMapper.incrReplyCount(momentId, 1);
        int insert = shareCommentReplyMapper.insert(shareCommentReply);
        return insert > 0;
    }
6.ShareMomentMapper.java 增加动态回复数
    /**
     * 增加动态回复数
     *
     * @param momentId
     * @param count
     */
    void incrReplyCount(@Param("momentId") Long momentId, @Param("count") int count);
7.ShareMomentMapper.xml
    <update id="incrReplyCount">
        update share_moment
        set reply_count = reply_count + #{count}
        where id = #{momentId}
          and is_deleted = 0
    </update>
8.测试
1.评论

CleanShot 2024-07-17 at 17.05.45@2x

2.评论记录增加

CleanShot 2024-07-17 at 17.07.07@2x

3.动态的回复数加一

CleanShot 2024-07-17 at 17.06.12@2x

4.回复 targetId 指向评论id

CleanShot 2024-07-17 at 17.09.02@2x

5.评论记录加一,并且parentId为被回复的评论的id

CleanShot 2024-07-17 at 17.11.23@2x

6.动态的回复数加一

CleanShot 2024-07-17 at 17.11.43@2x

2.查询树型评论回复

1.GetShareCommentReq.java
package com.sunxiansheng.circle.api.req;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
 * <p>
 * 鸡圈内容信息
 * </p>
 *
 * @author ChickenWing
 * @since 2024/05/16
 */
@Getter
@Setter
public class GetShareCommentReq implements Serializable {

    private Long id;

}

2.ShareCommentReplyVO.java
package com.sunxiansheng.circle.api.vo;

import lombok.Getter;
import lombok.Setter;

import javax.swing.tree.TreeNode;
import java.io.Serializable;
import java.util.List;

/**
 * <p>
 * 评论及回复信息
 * </p>
 *
 * @author ChickenWing
 * @since 2024/05/16
 */
@Getter
@Setter
public class ShareCommentReplyVO  implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    /**
     * 原始动态ID
     */
    private Long momentId;

    /**
     * 回复类型 1评论 2回复
     */
    private Integer replyType;

    /**
     * 内容
     */
    private String content;

    /**
     * 图片内容
     */
    private List<String> picUrlList;

    private String fromId;

    private String toId;

    private Long parentId;

    private String userName;

    private String avatar;

    private long createdTime;

    private List<ShareCommentReplyVO> children;

}

3.ShareCommentReplyController.java
    /**
     * 查询该动态下的评论
     */
    @PostMapping(value = "/list")
    public Result<List<ShareCommentReplyVO>> list(@RequestBody GetShareCommentReq req) {
        try {
            if (log.isInfoEnabled()) {
                log.info("获取鸡圈评论内容入参{}", JSON.toJSONString(req));
            }
            Preconditions.checkArgument(Objects.nonNull(req), "参数不能为空!");
            Preconditions.checkArgument(Objects.nonNull(req.getId()), "内容ID不能为空!");
            List<ShareCommentReplyVO> result = shareCommentReplyService.listComment(req);
            if (log.isInfoEnabled()) {
                log.info("获取鸡圈评论内容{}", JSON.toJSONString(result));
            }
            return Result.ok(result);
        } catch (IllegalArgumentException e) {
            log.error("参数异常!错误原因{}", e.getMessage(), e);
            return Result.fail(e.getMessage());
        } catch (Exception e) {
            log.error("获取鸡圈评论内容异常!错误原因{}", e.getMessage(), e);
            return Result.fail("获取鸡圈评论内容异常!");
        }
    }
4.ShareCommentReplyService.java
    /**
     * 查询某个动态的评论/回复
     *
     * @param req
     * @return
     */
    List<ShareCommentReplyVO> listComment(GetShareCommentReq req);
5.ShareCommentReplyServiceImpl.java(树工具具体使用)
    @Override
    public List<ShareCommentReplyVO> listComment(GetShareCommentReq req) {
        // 获取信息、
        Long momentId = req.getId();

        // 根据momentId来获取所有的评论以及回复
        ShareCommentReply shareCommentReply = new ShareCommentReply();
        shareCommentReply.setIsDeleted(0);
        shareCommentReply.setMomentId(Math.toIntExact(momentId));
        List<ShareCommentReply> shareCommentReplies = shareCommentReplyMapper.queryAllByLimit(shareCommentReply);

        // 将每个ShareCommentReply map成ShareCommentReplyVO 然后收集成集合
        List<ShareCommentReplyVO> list = shareCommentReplies.stream().map(
                shareCommentReplyItem -> {
                    ShareCommentReplyVO shareCommentReplyVO = new ShareCommentReplyVO();
                    shareCommentReplyVO.setId(shareCommentReplyItem.getId());
                    shareCommentReplyVO.setMomentId(Long.valueOf(shareCommentReplyItem.getMomentId()));
                    shareCommentReplyVO.setReplyType(shareCommentReplyItem.getReplyType());
                    shareCommentReplyVO.setContent(shareCommentReplyItem.getContent());
                    String picUrls = shareCommentReplyItem.getPicUrls();
                    if (StringUtils.isNotEmpty(picUrls)) {
                        // 反序列化
                        shareCommentReplyVO.setPicUrlList(JSON.parseArray(picUrls, String.class));
                    }
                    // 类型为回复,才需要设置
                    String createdBy = shareCommentReplyItem.getCreatedBy();
                    if (shareCommentReplyItem.getReplyType() == 2) {
                        shareCommentReplyVO.setFromId(createdBy);
                        shareCommentReplyVO.setToId(shareCommentReplyItem.getToUser());
                    }
                    shareCommentReplyVO.setParentId(shareCommentReplyItem.getParentId());
                    shareCommentReplyVO.setUserName(createdBy);
                    shareCommentReplyVO.setCreatedTime(shareCommentReplyItem.getCreatedTime().getTime());
                    // rpc查询头像
                    if (StringUtils.isNotEmpty(createdBy)) {
                        UserInfo userInfo = userRpc.getUserInfo(createdBy);
                        shareCommentReplyVO.setAvatar(userInfo.getAvatar());
                    }
                    return shareCommentReplyVO;
                }
        ).collect(Collectors.toList());

        // ============================== 构建树结构 ==============================
        // 1.创建树结构配置类,指定节点类型:ShareCommentReplyVO,id类型:Long
        TreeBuilderConfig<ShareCommentReplyVO, Long> treeBuilderConfig = new TreeBuilderConfig.Builder<ShareCommentReplyVO, Long>()
                // 设置childern的逻辑
                .withChildrenSetter(ShareCommentReplyVO::setChildren)
                // 根节点的parentId
                .withRootId(-1L)
                // ===以下几个参数都可以省略===
                // id提取器
                .withIdExtractor(ShareCommentReplyVO::getId)
                // 父id提取器
                .withParentIdExtractor(ShareCommentReplyVO::getParentId)
                // 是否开启递归构建
                .withRecursive(true)
                // 比较器
                .withComparator((a, b) -> {
                    // id升序排序
                    return a.getId() < b.getId() ? -1 : a.getId() > b.getId() ? 1 : 0;
                // 构建树
                }).build();
        // 2.构建树
        List<ShareCommentReplyVO> shareCommentReplyVOS = CategoryTreeBuilder.buildTree(list, treeBuilderConfig);
        // ============================== 构建树结构 ==============================

        return shareCommentReplyVOS;
    }
6.测试
1.apipost

CleanShot 2024-07-18 at 12.33.10@2x

2.由于还没使用网关,所以与用户信息有关的查不出来
7.自定义的树工具(支持构建树和查询子节点id)
1.CategoryTreeBuilder.java
package com.sunxiansheng.circle.server.util.bettertreeutils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 通用树结构构建器类
 * @param <T> 实体类的类型
 * @param <ID> 实体类的ID类型
 */
public class CategoryTreeBuilder<T, ID> {
    // 树构建器配置
    private final TreeBuilderConfig<T, ID> config;

    /**
     * 构造函数,初始化树构建器
     * @param config 树构建器的配置
     */
    public CategoryTreeBuilder(TreeBuilderConfig<T, ID> config) {
        this.config = config;

        // 检查在构建树时必要的字段是否为空
        if (config.isBuildForTree() && config.getChildrenSetter() == null) {
            throw new IllegalArgumentException("childrenSetter must be provided for building tree");
        }
    }

    /**
     * 静态方法,用于简化树构建过程
     * @param entities 实体列表:至少包含id,parentId,children(名字可以不同)
     * @param config 树构建器的配置详情:
     *               childrenSetter:设置子节点的逻辑(必填)
     *               ==============================================================
     *               idExtractor:id提取器,默认为调用getId()方法(选填)
     *               parentIdExtractor:父id提取器,默认为调用getParentId()方法(选填)
     *               rootId:根节点的parentId,默认-1(选填)
     *               comparator:比较器(选填)
     *               recursive:设置是否递归构建,false表示只构建父分类及其第一层子分类(选填)
     * @param <T> 实体类的类型
     * @param <ID> 实体类的ID类型
     * @return 树形结构列表
     */
    public static <T, ID> List<T> buildTree(List<T> entities, TreeBuilderConfig<T, ID> config) {
        return new CategoryTreeBuilder<>(config).buildTree(entities);
    }

    /**
     * 主方法,构建树结构
     * @param entities 实体列表
     * @return 树形结构列表
     */
    private List<T> buildTree(List<T> entities) {
        if (entities == null || entities.isEmpty()) {
            return Collections.emptyList();
        }

        // 使用 Map 存储父子关系
        Map<ID, List<T>> parentIdMap = entities.stream()
                .collect(Collectors.groupingBy(config.getParentIdExtractor()));

        // 筛选出根节点
        List<T> roots = parentIdMap.get(config.getRootId());
        if (roots == null) {
            roots = Collections.emptyList();
        }

        // 为每个根节点设置子节点
        roots.forEach(root -> {
            List<T> children = setChildren(root, parentIdMap);
            if (children != null) {
                config.getChildrenSetter().accept(root, children);
            }
        });

        // 对根节点进行排序
        if (config.getComparator() != null) {
            roots.sort(config.getComparator());
        }
        return roots;
    }

    /**
     * 递归方法,用于设置每个父节点的子节点
     * @param parent 父节点
     * @param parentIdMap 父子关系的 Map
     * @return 子节点列表,如果没有子节点则返回 null
     */
    private List<T> setChildren(T parent, Map<ID, List<T>> parentIdMap) {
        ID parentId = config.getIdExtractor().apply(parent);
        List<T> children = parentIdMap.get(parentId);

        // 如果没有子节点就返回null
        if (children == null || children.isEmpty()) {
            return null;
        }

        if (config.isRecursive()) {
            // 递归设置每个子节点的子节点
            children.forEach(child -> {
                List<T> subChildren = setChildren(child, parentIdMap);
                if (subChildren != null) {
                    config.getChildrenSetter().accept(child, subChildren);
                }
            });
        }

        // 对子节点进行排序
        if (config.getComparator() != null) {
            children.sort(config.getComparator());
        }

        return children;
    }

    /**
     * 根据节点ID递归查询其所有层级的子节点ID集合
     * 不传config的版本:默认IdExtractor为getId,ParentIdExtractor为getParentId。
     * @param entities 实体列表:不需要构建为树
     * @param parentId 父节点ID
     * @return 所有层级的子节点ID集合(没有子节点就返回空列表)
     */
    public static <T, ID> List<ID> getChildrenIds(List<T> entities, ID parentId) {
        TreeBuilderConfig<T, ID> defaultConfig = new TreeBuilderConfig.Builder<T, ID>().build();
        return getChildrenIds(entities, parentId, defaultConfig);
    }

    /**
     * 根据节点ID递归查询其所有层级的子节点ID集合(不包括当前节点id)
     * @param entities 实体列表:不需要构建为树
     * @param parentId 父节点ID
     * @param config 树构建器的配置
     *               idExtractor:id提取器,默认为调用getId()方法(选填)
     *               parentIdExtractor:父id提取器,默认为调用getParentId()方法(选填)
     * @return 所有层级的子节点ID集合(没有子节点就返回空列表)
     */
    public static <T, ID> List<ID> getChildrenIds(List<T> entities, ID parentId, TreeBuilderConfig<T, ID> config) {
        List<ID> result = new ArrayList<>();
        getAllChildrenIdsHelper(entities, parentId, config, result);
        return result;
    }

    private static <T, ID> void getAllChildrenIdsHelper(List<T> entities, ID parentId, TreeBuilderConfig<T, ID> config, List<ID> result) {
        // 使用默认提取器
        Function<T, ID> idExtractor = config.getIdExtractor() != null ? config.getIdExtractor() : TreeBuilderConfig.getDefaultIdExtractor();
        Function<T, ID> parentIdExtractor = config.getParentIdExtractor() != null ? config.getParentIdExtractor() : TreeBuilderConfig.getDefaultParentIdExtractor();

        List<ID> directChildrenIds = entities.stream()
                .filter(entity -> parentIdExtractor.apply(entity).equals(parentId))
                .map(idExtractor)
                .collect(Collectors.toList());

        if (!directChildrenIds.isEmpty()) {
            result.addAll(directChildrenIds);
            for (ID childId : directChildrenIds) {
                getAllChildrenIdsHelper(entities, childId, config, result);
            }
        }
    }

    /**
     * 查询当前节点及其所有子节点的ID集合(包括当前节点id)
     * 使用默认的配置:默认IdExtractor为getId,ParentIdExtractor为getParentId。
     * @param entities 实体列表:不需要构建为树
     * @param nodeId 当前节点ID
     * @return 当前节点及其所有子节点的ID集合(没有子节点就返回空列表)
     */
    public static <T, ID> List<ID> getNodeAndChildrenIds(List<T> entities, ID nodeId) {
        TreeBuilderConfig<T, ID> defaultConfig = new TreeBuilderConfig.Builder<T, ID>().build();
        return getNodeAndChildrenIds(entities, nodeId, defaultConfig);
    }

    /**
     * 查询当前节点及其所有子节点的ID集合(包括当前节点id)
     * @param entities 实体列表:不需要构建为树
     * @param nodeId 当前节点ID
     * @param config 树构建器的配置
     *               idExtractor:id提取器,默认为调用getId()方法(选填)
     *               parentIdExtractor:父id提取器,默认为调用getParentId()方法(选填)
     * @return 当前节点及其所有子节点的ID集合(没有子节点就返回空列表)
     */
    public static <T, ID> List<ID> getNodeAndChildrenIds(List<T> entities, ID nodeId, TreeBuilderConfig<T, ID> config) {
        List<ID> result = new ArrayList<>();
        result.add(nodeId);
        getAllChildrenIdsHelper(entities, nodeId, config, result);
        return result;
    }
}
2.TreeBuilderConfig.java
package com.sunxiansheng.circle.server.util.bettertreeutils;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * 树构建器配置类
 * @param <T> 实体类的类型
 * @param <ID> 实体类的ID类型
 */
public class TreeBuilderConfig<T, ID> {
    private final Function<T, ID> idExtractor;
    private final Function<T, ID> parentIdExtractor;
    private final BiConsumer<T, List<T>> childrenSetter;
    private final Comparator<T> comparator;
    private final ID rootId;
    private final boolean recursive;
    private final boolean buildForTree;

    /**
     * 私有构造函数,使用Builder模式进行构建
     * @param builder Builder对象
     */
    private TreeBuilderConfig(Builder<T, ID> builder) {
        this.idExtractor = builder.idExtractor;
        this.parentIdExtractor = builder.parentIdExtractor;
        this.childrenSetter = builder.childrenSetter;
        this.comparator = builder.comparator;
        this.rootId = builder.rootId;
        this.recursive = builder.recursive;
        this.buildForTree = builder.buildForTree;

        // 检查在构建树时必要的字段是否为空
        if (this.buildForTree && this.childrenSetter == null) {
            throw new IllegalArgumentException("childrenSetter must be provided for building tree");
        }
    }

    public Function<T, ID> getIdExtractor() {
        return idExtractor;
    }

    public Function<T, ID> getParentIdExtractor() {
        return parentIdExtractor;
    }

    public BiConsumer<T, List<T>> getChildrenSetter() {
        return childrenSetter;
    }

    public Comparator<T> getComparator() {
        return comparator;
    }

    public ID getRootId() {
        return rootId;
    }

    public boolean isRecursive() {
        return recursive;
    }

    public boolean isBuildForTree() {
        return buildForTree;
    }

    /**
     * Builder类,用于构建TreeBuilderConfig实例
     * @param <T> 实体类的类型
     * @param <ID> 实体类的ID类型
     */
    public static class Builder<T, ID> {
        private Function<T, ID> idExtractor = getDefaultIdExtractor();
        private Function<T, ID> parentIdExtractor = getDefaultParentIdExtractor();
        private BiConsumer<T, List<T>> childrenSetter;
        private Comparator<T> comparator;
        private ID rootId = (ID) Long.valueOf(-1);
        private boolean recursive = true; // 默认递归
        private boolean buildForTree = false; // 标识是否用于构建树

        /**
         * 设置ID提取器
         * @param idExtractor ID提取器
         * @return Builder对象
         */
        public Builder<T, ID> withIdExtractor(Function<T, ID> idExtractor) {
            this.idExtractor = idExtractor;
            return this;
        }

        /**
         * 设置父ID提取器
         * @param parentIdExtractor 父ID提取器
         * @return Builder对象
         */
        public Builder<T, ID> withParentIdExtractor(Function<T, ID> parentIdExtractor) {
            this.parentIdExtractor = parentIdExtractor;
            return this;
        }

        /**
         * 设置子节点列表设置器
         * @param childrenSetter 子节点列表设置器
         * @return Builder对象
         */
        public Builder<T, ID> withChildrenSetter(BiConsumer<T, List<T>> childrenSetter) {
            this.childrenSetter = childrenSetter;
            // 标识是否用于构建树
            this.buildForTree = true;
            return this;
        }

        /**
         * 设置节点比较器
         * @param comparator 节点比较器
         * @return Builder对象
         */
        public Builder<T, ID> withComparator(Comparator<T> comparator) {
            this.comparator = comparator;
            return this;
        }

        /**
         * 设置根节点ID
         * @param rootId 根节点ID
         * @return Builder对象
         */
        public Builder<T, ID> withRootId(ID rootId) {
            this.rootId = rootId;
            return this;
        }

        /**
         * 设置是否递归
         * @param recursive 是否递归
         * @return Builder对象
         */
        public Builder<T, ID> withRecursive(boolean recursive) {
            this.recursive = recursive;
            return this;
        }

        /**
         * 构建TreeBuilderConfig实例
         * @return TreeBuilderConfig实例
         */
        public TreeBuilderConfig<T, ID> build() {
            return new TreeBuilderConfig<>(this);
        }
    }

    /**
     * 获取默认的ID提取器
     * @return 默认的ID提取器
     */
    @SuppressWarnings("unchecked")
    public static <T, ID> Function<T, ID> getDefaultIdExtractor() {
        return entity -> {
            try {
                Method getIdMethod = entity.getClass().getMethod("getId");
                return (ID) getIdMethod.invoke(entity);
            } catch (Exception e) {
                throw new RuntimeException("Failed to get ID from entity: " + entity.getClass().getName(), e);
            }
        };
    }

    /**
     * 获取默认的父ID提取器
     * @return 默认的父ID提取器
     */
    @SuppressWarnings("unchecked")
    public static <T, ID> Function<T, ID> getDefaultParentIdExtractor() {
        return entity -> {
            try {
                Method getParentIdMethod = entity.getClass().getMethod("getParentId");
                return (ID) getParentIdMethod.invoke(entity);
            } catch (Exception e) {
                throw new RuntimeException("Failed to get Parent ID from entity: " + entity.getClass().getName(), e);
            }
        };
    }
}

3.删除评论回复

1.RemoveShareCommentReq.java
package com.sunxiansheng.circle.api.req;

import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

/**
 * <p>
 * 鸡圈内容信息
 * </p>
 *
 * @author ChickenWing
 * @since 2024/05/16
 */
@Getter
@Setter
public class RemoveShareCommentReq implements Serializable {

    /**
     * 要删除的评论id
     */
    private Long id;

    /**
     * 回复类型 1评论 2回复
     */
    private Integer replyType;

}

2.ShareCommentReplyController.java
    /**
     * 删除鸡圈评论内容
     */
    @PostMapping(value = "/remove")
    public Result<Boolean> remove(@RequestBody RemoveShareCommentReq req) {
        try {
            if (log.isInfoEnabled()) {
                log.info("删除鸡圈评论内容入参{}", JSON.toJSONString(req));
            }
            Preconditions.checkArgument(Objects.nonNull(req), "参数不能为空!");
            Preconditions.checkArgument(Objects.nonNull(req.getReplyType()), "类型不能为空!");
            Preconditions.checkArgument(Objects.nonNull(req.getId()), "内容ID不能为空!");
            Boolean result = shareCommentReplyService.removeComment(req);
            if (log.isInfoEnabled()) {
                log.info("删除鸡圈评论内容{}", JSON.toJSONString(result));
            }
            return Result.ok(result);
        } catch (IllegalArgumentException e) {
            log.error("参数异常!错误原因{}", e.getMessage(), e);
            return Result.fail(e.getMessage());
        } catch (Exception e) {
            log.error("删除鸡圈评论内容异常!错误原因{}", e.getMessage(), e);
            return Result.fail("删除鸡圈评论内容异常!");
        }
    }

3.ShareCommentReplyService.java
    /**
     * 删除评论/回复
     * @param req
     * @return
     */
    Boolean removeComment(RemoveShareCommentReq req);
4.ShareCommentReplyServiceImpl.java(根据id查询所有子id具体使用)
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean removeComment(RemoveShareCommentReq req) {
        Long id = req.getId();
        Integer replyType = req.getReplyType();
        // 根据id查询出该评论
        ShareCommentReply queryById = this.shareCommentReplyMapper.queryById(id);
        // 得到该评论的momentId
        Integer momentId = queryById.getMomentId();
        // 根据momentId来获取所有的评论以及回复
        ShareCommentReply shareCommentReply = new ShareCommentReply();
        shareCommentReply.setIsDeleted(0);
        shareCommentReply.setMomentId(Math.toIntExact(momentId));
        List<ShareCommentReply> shareCommentReplies = shareCommentReplyMapper.queryAllByLimit(shareCommentReply);
        // 根据id查询出所有的子评论id
        List<Long> childrenIds = CategoryTreeBuilder.getNodeAndChildrenIds(shareCommentReplies, id);
        // 批量逻辑删除
        this.shareCommentReplyMapper.updateBatchByIds(childrenIds);
        // 减少动态的评论数量
        int count = childrenIds.size();
        this.shareMomentMapper.incrReplyCount(Long.valueOf(momentId), -count);
        return true;
    }
5.ShareCommentReplyMapper.java
    /**
     * 根据id批量更新
     *
     * @param ids
     */
    void updateBatchByIds(@Param("ids") List<Long> ids);
6.ShareCommentReplyMapper.xml
    <update id="updateBatchByIds">
        update share_comment_reply
        set is_deleted = 1
        where id in
        <foreach collection="ids" open="(" separator="," close=")" item="item">
            #{item}
        </foreach>
    </update>
7.测试
1.apipost

CleanShot 2024-07-18 at 15.09.46@2x

2.评论表删除成功

CleanShot 2024-07-18 at 15.10.16@2x

3.回复数量删除成功

CleanShot 2024-07-18 at 15.11.06@2x

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

S-X-S

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值