weblog/doc/14、消息中间件 RocketMQ/14.7 笔记置顶取消置顶接口开发.md
2025-02-17 11:57:55 +08:00

9.5 KiB
Raw Blame History

title, url, publishedTime
title url publishedTime
笔记置顶/取消置顶接口开发 - 犬小哈专栏 https://www.quanxiaoha.com/column/10357.html null

本小节中,我们将把笔记置顶、取消置顶接口开发完成,注意,这个接口同时支持置顶与取消置顶操作。

接口定义

接口地址

POST /note/top

入参

{
    "id": 1829410872473157676, // 笔记 ID
    "isTop": true // 是否置顶true 表示置顶操作false 表示取消置顶操作
}

出参

{
	"success": true, // true 表示更新成功
	"message": null,
	"errorCode": null,
	"data": null
}

创建接口入参 VO

编辑 xiaohashu-note-biz 模块,在 /model/vo 包下,新增 TopNoteReqVO 入参实体类,代码如下:

package com.quanxiaoha.xiaohashu.note.biz.model.vo;

import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author: 犬小哈
 * @date: 2024/4/7 15:17
 * @version: v1.0.0
 * @description: 笔记置顶/取消置顶
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class TopNoteReqVO {

    @NotNull(message = "笔记 ID 不能为空")
    private Long id;

    @NotNull(message = "置顶状态不能为空")
    private Boolean isTop;

}

新增 mapper 更新方法

接着,编辑 NoteDOMapper 接口,添加一个更新笔记置顶状态的方法,代码如下:

package com.quanxiaoha.xiaohashu.note.biz.domain.mapper;

import com.quanxiaoha.xiaohashu.note.biz.domain.dataobject.NoteDO;

public interface NoteDOMapper {
	// 省略...

    int updateIsTop(NoteDO noteDO);

}

在接口对应的 xml 映射文件中,添加更新 SQL 语句,代码如下:

  <update id="updateIsTop" parameterType="com.quanxiaoha.xiaohashu.note.biz.domain.dataobject.NoteDO">
    update t_note
    set is_top = #{isTop,jdbcType=BIT},
        update_time = #{updateTime,jdbcType=TIMESTAMP}
    where id = #{id,jdbcType=BIGINT} and creator_id = #{creatorId,jdbcType=BIGINT}
  </update>

Tip

: 更新条件中,除了需要传入笔记 ID, 还需要传入笔记的发布者 ID, 用于判断当前登录的用户,是否和发布者是同一个人,只有笔记的作者才能置顶、取消置顶自己的笔记。

添加异常码枚举值

编辑 ResponseCodeEnum 全局枚举类,添加一个您无法操作该笔记的异常码枚举值,业务层判断需要用到:

package com.quanxiaoha.xiaohashu.note.biz.enums;

import com.quanxiaoha.framework.common.exception.BaseExceptionInterface;
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @author: 犬小哈
 * @url: www.quanxiaoha.com
 * @date: 2023-08-15 10:33
 * @description: 响应异常码
 **/
@Getter
@AllArgsConstructor
public enum ResponseCodeEnum implements BaseExceptionInterface {

    // 省略...

    // ----------- 业务异常状态码 -----------
    // 省略...
    NOTE_CANT_OPERATE("NOTE-20007", "您无法操作该笔记"),
    ;

    // 省略...

}

编辑 service 业务层

NoteService 笔记业务层接口中,声明一个笔记置顶 / 取消置顶方法,代码如下:

package com.quanxiaoha.xiaohashu.note.biz.service;

import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.xiaohashu.note.biz.model.vo.*;

/**
 * @author: 犬小哈
 * @date: 2024/4/7 15:41
 * @version: v1.0.0
 * @description: 笔记业务
 **/
public interface NoteService {

	// 省略...

    /**
     * 笔记置顶 / 取消置顶
     * @param topNoteReqVO
     * @return
     */
    Response<?> topNote(TopNoteReqVO topNoteReqVO);
}

在其实现类中,实现上述方法,代码如下:

package com.quanxiaoha.xiaohashu.note.biz.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.quanxiaoha.framework.biz.context.holder.LoginUserContextHolder;
import com.quanxiaoha.framework.common.exception.BizException;
import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.framework.common.util.JsonUtils;
import com.quanxiaoha.xiaohashu.note.biz.constant.MQConstants;
import com.quanxiaoha.xiaohashu.note.biz.constant.RedisKeyConstants;
import com.quanxiaoha.xiaohashu.note.biz.domain.dataobject.NoteDO;
import com.quanxiaoha.xiaohashu.note.biz.domain.mapper.NoteDOMapper;
import com.quanxiaoha.xiaohashu.note.biz.domain.mapper.TopicDOMapper;
import com.quanxiaoha.xiaohashu.note.biz.enums.NoteStatusEnum;
import com.quanxiaoha.xiaohashu.note.biz.enums.NoteTypeEnum;
import com.quanxiaoha.xiaohashu.note.biz.enums.NoteVisibleEnum;
import com.quanxiaoha.xiaohashu.note.biz.enums.ResponseCodeEnum;
import com.quanxiaoha.xiaohashu.note.biz.model.vo.*;
import com.quanxiaoha.xiaohashu.note.biz.rpc.DistributedIdGeneratorRpcService;
import com.quanxiaoha.xiaohashu.note.biz.rpc.KeyValueRpcService;
import com.quanxiaoha.xiaohashu.note.biz.rpc.UserRpcService;
import com.quanxiaoha.xiaohashu.note.biz.service.NoteService;
import com.quanxiaoha.xiaohashu.user.dto.resp.FindUserByIdRspDTO;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @author: 犬小哈
 * @date: 2024/4/7 15:41
 * @version: v1.0.0
 * @description: 笔记业务
 **/
@Service
@Slf4j
public class NoteServiceImpl implements NoteService {

    // 省略...

    /**
     * 笔记置顶 / 取消置顶
     *
     * @param topNoteReqVO
     * @return
     */
    @Override
    public Response<?> topNote(TopNoteReqVO topNoteReqVO) {
        // 笔记 ID
        Long noteId = topNoteReqVO.getId();
        // 是否置顶
        Boolean isTop = topNoteReqVO.getIsTop();

        // 当前登录用户 ID
        Long currUserId = LoginUserContextHolder.getUserId();

        // 构建置顶/取消置顶 DO 实体类
        NoteDO noteDO = NoteDO.builder()
                .id(noteId)
                .isTop(isTop)
                .updateTime(LocalDateTime.now())
                .creatorId(currUserId) // 只有笔记所有者,才能置顶/取消置顶笔记
                .build();

        int count = noteDOMapper.updateIsTop(noteDO);

        if (count == 0) {
            throw new BizException(ResponseCodeEnum.NOTE_CANT_OPERATE);
        }

        // 删除 Redis 缓存
        String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
        redisTemplate.delete(noteDetailRedisKey);

        // 同步发送广播模式 MQ将所有实例中的本地缓存都删除掉
        rocketMQTemplate.syncSend(MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE, noteId);
        log.info("====> MQ删除笔记本地缓存发送成功...");

        return Response.success();
    }

	// 省略...


}

解释一波业务逻辑:

  • 获取入参传过来的笔记 ID, 操作标识,以及从上下文中获取当前登录用户的 ID;
  • 构建置顶/取消置顶 DO 实体类;
  • 执行更新语句;
  • 判断影响的行数,若为 0 说明当前笔记不允许被操作;
  • 否则,删除 Redis 缓存,并同步发送广播模式 MQ将本地缓存也删除

新增 controller 接口

最后,编辑 NoteController 控制器,添加 /note/top 接口,代码如下:

package com.quanxiaoha.xiaohashu.note.biz.controller;

import com.quanxiaoha.framework.biz.operationlog.aspect.ApiOperationLog;
import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.xiaohashu.note.biz.model.vo.*;
import com.quanxiaoha.xiaohashu.note.biz.service.NoteService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: 犬小哈
 * @date: 2024/4/4 13:22
 * @version: v1.0.0
 * @description: 笔记
 **/
@RestController
@RequestMapping("/note")
@Slf4j
public class NoteController {

  	// 省略...

    @PostMapping(value = "/top")
    @ApiOperationLog(description = "置顶/取消置顶笔记")
    public Response<?> topNote(@Validated @RequestBody TopNoteReqVO topNoteReqVO) {
        return noteService.topNote(topNoteReqVO);
    }

}

自测一波

重启笔记服务,自测一下接口功能是否正常,如下图所示:

服务端响应成功,同时,别忘了确认一下数据库中的笔记数据是否更新成功,以及二级缓存是否被删除。

本小节源码下载

https://t.zsxq.com/sHf7b