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

news2024/11/15 19:58:36

文章目录

    • 1.新增评论回复
        • 1.EasyCode生成代码
        • 2.SaveShareCommentReplyReq.java
        • 3.ShareCommentReplyController.java
        • 4.ShareCommentReplyService.java
        • 5.ShareCommentReplyServiceImpl.java
        • 6.ShareMomentMapper.java 增加动态回复数
        • 7.ShareMomentMapper.xml
        • 8.测试
          • 1.评论
          • 2.评论记录增加
          • 3.动态的回复数加一
          • 4.回复 targetId 指向评论id
          • 5.评论记录加一,并且parentId为被回复的评论的id
          • 6.动态的回复数加一
    • 2.查询树型评论回复
        • 1.GetShareCommentReq.java
        • 2.ShareCommentReplyVO.java
        • 3.ShareCommentReplyController.java
        • 4.ShareCommentReplyService.java
        • 5.ShareCommentReplyServiceImpl.java(树工具具体使用)
        • 6.测试
          • 1.apipost
          • 2.由于还没使用网关,所以与用户信息有关的查不出来
        • 7.自定义的树工具(支持构建树和查询子节点id)
          • 1.CategoryTreeBuilder.java
          • 2.TreeBuilderConfig.java
    • 3.删除评论回复
        • 1.RemoveShareCommentReq.java
        • 2.ShareCommentReplyController.java
        • 3.ShareCommentReplyService.java
        • 4.ShareCommentReplyServiceImpl.java(根据id查询所有子id具体使用)
        • 5.ShareCommentReplyMapper.java
        • 6.ShareCommentReplyMapper.xml
        • 7.测试
          • 1.apipost
          • 2.评论表删除成功
          • 3.回复数量删除成功

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

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

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

相关文章

CTFSHOW misc入门中misc8解法

第一步:下载misc8压缩包后解压缩&#xff0c;发现是一张misc8.png图片 第二步&#xff1a;老规矩&#xff0c;还是在kali里用binwalk命令查看文件是否包含隐藏文件&#xff0c;发现有两个png文件 第三步&#xff1a;使用binwalk -e 命令看是否能进行解析文件&#xff0c;结果不…

Emacs29.x版本之重要特性及用法实例(一百六十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

如何提升网络对AI大模型流量的承载能力?

前言 随着AI大模型的兴起&#xff0c;数据中心正在经历前所未有的变革。AI模型的规模巨大并持续快速增长。自2017年起&#xff0c;AI模型的规模每半年翻一番&#xff0c;从初代Transformer的6500万增长到GPT-4的1.76万亿&#xff0c;预计下一代大语言模型的尺寸将达到10万亿。…

CSE12 Lab 4: Simple CSV File Analysis

This file shows the stock returns from an investment portfolio over a year. The “A” column contains the stock name and the “B” column indicates the returns in USD (You can assume that there are no negative stock returns in any of our CSV data files ).…

深度解析|comfyui画面词云效果工作流搭建和讲解

前言 本篇文章共分为4部分&#xff1a; 工作流展示 工作流节点逻辑梳理 工作流拆解搭建 总结 我会对里面的重要的节点做详细的说明和解释&#xff0c;希望大家看完之后能学会并自己进行搭建&#xff0c;可以把出图效果放评论区一起学习探讨哦。 1.工作流效果展示 今天我…

45.【C语言】指针(重难点)(H)

目录&#xff1a; 22.函数指针变量 *创建 *使用 *两段代码分析 23.函数指针数组 *基本用法 *作用 往期推荐 22.函数指针变量 *创建 类比数组指针变量的定义&#xff1a;存放数组地址的指针变量&#xff0c;同理函数指针变量存放函数的地址 格式 函数的返回类型 (*指针变量的…

【python报错已解决】“string indices must be integers”

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言 你是否在处理Python字符串时遇到了“string indices must be integers”的错误&#xff1f;这个错误可能会让你的代码运行…

[SWPU2019]Network

TTL加密 python脚本解密 import binasciif open(attachment_3.txt, "r") str Binary number while 1:num f.readline()if not num:breakif num.rstrip() 63: # 去掉每行后面的空格Binary 00elif num.rstrip() 127:Binary 01elif num.rstrip() 191:Bi…

C++那些事之helgrind并发编程检测

C那些事之helgrind并发编程检测 大纲 死锁数据竞争提问 通常我们在写多线程程序的时候很容易遇到两个问题&#xff1a; 死锁了&#xff0c;不知道什么原因导致数据不一致&#xff0c;多个线程没保护数据 那么有没有工具来检测这两种场景呢 答案是有的&#xff0c;我们可以使用v…

【Qt】常用控件QProgreeBar

常用控件QProgreeBar 使用QProgressBar表示一个进度条&#xff01;&#xff01;&#xff01; QProgressBar的核心属性 属性说明 minimum 进度条最⼩值 maximum 进度条最⼤值 value 进度条当前值 alignment ⽂本在进度条中的对⻬⽅式. Qt::AlignLeft : 左对⻬Qt::Align…

Unity(2022.3.38LTS) - 性能分析器

目录 一. 简介 二. 打开分析器 1. 打开 2.在目标平台上分析应用程序 三. 分析分析器 四. 模块详细介绍 1.Asset Loading Profiler 模块 2.Audio Profiler 模块 3.CPU Usage Profiler 模块 4.File Access Profiler 模块 5.Global Illumination Profiler 模块 6.GPU …

多线程任务中设置MDC的实践

多线程任务中设置MDC的实践 引言 在当今的软件开发中&#xff0c;日志记录是不可或缺的一部分。日志不仅仅是调试工具&#xff0c;还在系统监控、性能分析、故障排除中扮演着关键角色。尤其在多线程环境中&#xff0c;日志的上下文信息一致性至关重要。MDC&#xff08;Mapped…

WPF 动画 插值动画、关键帧动画、路径动画

WPF动画&#xff0c;分为三种&#xff1a;插值动画、关键帧动画、路径动画 2.1 插值动画&#xff1a;     1&#xff09;定义&#xff1a;插值动画是指&#xff0c;属性值从某一个值&#xff0c;经过一段时间后&#xff0c;连续变化值另一个值的动画。         例…

订单到期关闭如何实现?

目录 一、被动关闭 二、定时任务 三、JDK自带的DelayQueue 四、Netty的时间轮 五、Kafka的时间轮 六、RocketMQ延迟消息 七、RabbitMQ死信队列 八、RabbitMQ插件 九、Redis过期监听 十、Redis的Zset 十一、Redisson 在电商、支付等系统中&#xff0c;一般都是先创建…

win/mac数字资产管理软件Adobe Bridge (BR)软件下载安装

目录 一、Adobe BR软件介绍 1.1 软件概述 1.2 主要功能 1.3 系统要求 二、Adobe BR安装步骤 2.1 下载软件 2.2 安装前准备 2.3 安装过程 三、Adobe BR使用教程 3.1 基础操作 3.1.1 浏览与预览 3.1.2 搜索与筛选 3.1.3 批量操作 3.2 进阶功能 3.2.1 元数据管理 …

鸿蒙OS高级应用开发例题

44项目需要同时进行应用和元服务的开发&#xff0c;并针对当前项目工程中的代码可以分别构建出应用和元服务的包&#xff0c;如何在DevEco Studio中设置不同的构建配置&#xff0c;达成这个目的 A. 在模块级别buld-pronlejson5定义两个target;将两个target的bundleType分别设置…

8.20模拟赛题解

简单点评一下 整体上来看 &#xff0c;A题拿满分的同学可能占一半吧 &#xff0c;这个数据其实是不太理想的 &#xff0c;说明同学们对于思维模拟题还是不熟练&#xff0c;没抓住题目要分析的本质。 B题显然是保证有解的&#xff0c;有解的情况下问最优解&#xff0c;说明翻到满…

动力电池系统面向开发的测试——电池阻抗特性测试(下)

接动力电池系统面向开发的测试——开路电压测试&#xff08;上&#xff09;本文主要围绕BMS设计中的等效电路模型第二大动态特性参数——阻抗特性及测试内容来展开分享。 阻抗特性测试方法&#xff1a; 方案1&#xff1a;直流脉冲测试 前面在分享功率评估内容的时候&#xf…

【正点原子K210连载】第三十二章 音频FFT实验 摘自【正点原子】DNK210使用指南-CanMV版指南

第三十二章 音频FFT实验 本章将介绍CanMV下FFT的应用&#xff0c;通过将时域采集到的音频数据通过FFT为频域。通过本章的学习&#xff0c;读者将学习到CanMV下控制FFT加速器进行FFT的使用。 本章分为如下几个小节&#xff1a; 32.1 maix.FFT模块介绍 32.2 硬件设计 32.3 程序设…

How can OpenAI Gym‘s visualizations work within Docker?

题意&#xff1a;OpenAI Gym 的可视化功能如何在 Docker 中运行&#xff1f; 问题背景&#xff1a; Id like to get OpenAI Gym working with the rendered OpenGL visualizations within a docker container. 我想在 Docker 容器中让 OpenAI Gym 与渲染的 OpenGL 可视化一起…