feat(note): 实现笔记点赞功能
- 新增笔记点赞接口,支持用户对笔记进行点赞操作 - 集成 Redis 布隆过滤器,用于高效判断用户是否已点赞 - 添加 Lua 脚本处理点赞逻辑,包括布隆过滤器检查与更新 - 实现异步批量初始化布隆过滤器,提升性能与用户体验 - 完善点赞相关枚举、VO 类及 Redis Key 常量定义 - 在 HTTP 客户端中新增点赞接口测试用例 - 增加笔记存在性校验逻辑,确保操作目标有效 - 添加点赞状态枚举和响应码,优化错误提示信息
This commit is contained in:
@@ -7,6 +7,11 @@ public class RedisKeyConstants {
|
|||||||
*/
|
*/
|
||||||
public static final String NOTE_DETAIL_KEY = "note:detail:";
|
public static final String NOTE_DETAIL_KEY = "note:detail:";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 布隆过滤器:用户笔记点赞
|
||||||
|
*/
|
||||||
|
public static final String BLOOM_USER_NOTE_LIKE_LIST_KEY = "bloom:note:likes:";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建完整的笔记详情 KEY
|
* 构建完整的笔记详情 KEY
|
||||||
@@ -17,4 +22,14 @@ public class RedisKeyConstants {
|
|||||||
return NOTE_DETAIL_KEY + noteId;
|
return NOTE_DETAIL_KEY + noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建完整的布隆过滤器:用户笔记点赞 KEY
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 布隆过滤器:用户笔记点赞 KEY
|
||||||
|
*/
|
||||||
|
public static String buildBloomUserNoteLikeListKey(Long userId) {
|
||||||
|
return BLOOM_USER_NOTE_LIKE_LIST_KEY + userId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -56,4 +56,10 @@ public class NoteController {
|
|||||||
return noteService.topNote(topNoteReqVO);
|
return noteService.topNote(topNoteReqVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/like")
|
||||||
|
@ApiOperationLog(description = "点赞笔记")
|
||||||
|
public Response<?> likeNote(@Validated @RequestBody LikeNoteReqVO likeNoteReqVO) {
|
||||||
|
return noteService.likeNote(likeNoteReqVO);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.hanserwei.hannote.note.biz.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum LikeStatusEnum {
|
||||||
|
LIKE(1), // 点赞
|
||||||
|
DISLIKE(0), // 取消点赞
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.hanserwei.hannote.note.biz.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum NoteLikeLuaResultEnum {
|
||||||
|
// 布隆过滤器不存在
|
||||||
|
BLOOM_NOT_EXIST(-1L),
|
||||||
|
// 笔记已点赞
|
||||||
|
NOTE_LIKED(1L),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Long code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型 code 获取对应的枚举
|
||||||
|
*
|
||||||
|
* @param code 类型 code
|
||||||
|
* @return 类型枚举
|
||||||
|
*/
|
||||||
|
public static NoteLikeLuaResultEnum valueOf(Long code) {
|
||||||
|
for (NoteLikeLuaResultEnum noteLikeLuaResultEnum : NoteLikeLuaResultEnum.values()) {
|
||||||
|
if (Objects.equals(code, noteLikeLuaResultEnum.getCode())) {
|
||||||
|
return noteLikeLuaResultEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
|
|||||||
TOPIC_NOT_FOUND("NOTE-20005", "话题不存在"),
|
TOPIC_NOT_FOUND("NOTE-20005", "话题不存在"),
|
||||||
NOTE_CANT_VISIBLE_ONLY_ME("NOTE-20006", "此笔记无法修改为仅自己可见"),
|
NOTE_CANT_VISIBLE_ONLY_ME("NOTE-20006", "此笔记无法修改为仅自己可见"),
|
||||||
NOTE_CANT_OPERATE("NOTE-20007", "您无法操作该笔记"),
|
NOTE_CANT_OPERATE("NOTE-20007", "您无法操作该笔记"),
|
||||||
|
NOTE_ALREADY_LIKED("NOTE-20008", "您已经点赞过该笔记"),
|
||||||
;
|
;
|
||||||
|
|
||||||
// 异常码
|
// 异常码
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.hanserwei.hannote.note.biz.model.vo;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
public class LikeNoteReqVO {
|
||||||
|
|
||||||
|
@NotNull(message = "笔记 ID 不能为空")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -49,4 +49,12 @@ public interface NoteService extends IService<NoteDO> {
|
|||||||
*/
|
*/
|
||||||
Response<?> topNote(TopNoteReqVO topNoteReqVO);
|
Response<?> topNote(TopNoteReqVO topNoteReqVO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 点赞笔记
|
||||||
|
*
|
||||||
|
* @param likeNoteReqVO 点赞笔记请求
|
||||||
|
* @return 点赞笔记结果
|
||||||
|
*/
|
||||||
|
Response<?> likeNote(LikeNoteReqVO likeNoteReqVO);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
|
||||||
import com.hanserwei.framework.common.exception.ApiException;
|
import com.hanserwei.framework.common.exception.ApiException;
|
||||||
import com.hanserwei.framework.common.response.Response;
|
import com.hanserwei.framework.common.response.Response;
|
||||||
@@ -14,16 +15,15 @@ import com.hanserwei.framework.common.utils.JsonUtils;
|
|||||||
import com.hanserwei.hannote.note.biz.constant.MQConstants;
|
import com.hanserwei.hannote.note.biz.constant.MQConstants;
|
||||||
import com.hanserwei.hannote.note.biz.constant.RedisKeyConstants;
|
import com.hanserwei.hannote.note.biz.constant.RedisKeyConstants;
|
||||||
import com.hanserwei.hannote.note.biz.domain.dataobject.NoteDO;
|
import com.hanserwei.hannote.note.biz.domain.dataobject.NoteDO;
|
||||||
|
import com.hanserwei.hannote.note.biz.domain.dataobject.NoteLikeDO;
|
||||||
import com.hanserwei.hannote.note.biz.domain.dataobject.TopicDO;
|
import com.hanserwei.hannote.note.biz.domain.dataobject.TopicDO;
|
||||||
import com.hanserwei.hannote.note.biz.domain.mapper.NoteDOMapper;
|
import com.hanserwei.hannote.note.biz.domain.mapper.NoteDOMapper;
|
||||||
import com.hanserwei.hannote.note.biz.enums.NoteStatusEnum;
|
import com.hanserwei.hannote.note.biz.enums.*;
|
||||||
import com.hanserwei.hannote.note.biz.enums.NoteTypeEnum;
|
|
||||||
import com.hanserwei.hannote.note.biz.enums.NoteVisibleEnum;
|
|
||||||
import com.hanserwei.hannote.note.biz.enums.ResponseCodeEnum;
|
|
||||||
import com.hanserwei.hannote.note.biz.model.vo.*;
|
import com.hanserwei.hannote.note.biz.model.vo.*;
|
||||||
import com.hanserwei.hannote.note.biz.rpc.DistributedIdGeneratorRpcService;
|
import com.hanserwei.hannote.note.biz.rpc.DistributedIdGeneratorRpcService;
|
||||||
import com.hanserwei.hannote.note.biz.rpc.KeyValueRpcService;
|
import com.hanserwei.hannote.note.biz.rpc.KeyValueRpcService;
|
||||||
import com.hanserwei.hannote.note.biz.rpc.UserRpcService;
|
import com.hanserwei.hannote.note.biz.rpc.UserRpcService;
|
||||||
|
import com.hanserwei.hannote.note.biz.service.NoteLikeDOService;
|
||||||
import com.hanserwei.hannote.note.biz.service.NoteService;
|
import com.hanserwei.hannote.note.biz.service.NoteService;
|
||||||
import com.hanserwei.hannote.note.biz.service.TopicDOService;
|
import com.hanserwei.hannote.note.biz.service.TopicDOService;
|
||||||
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
|
||||||
@@ -34,14 +34,18 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.apache.rocketmq.client.producer.SendCallback;
|
import org.apache.rocketmq.client.producer.SendCallback;
|
||||||
import org.apache.rocketmq.client.producer.SendResult;
|
import org.apache.rocketmq.client.producer.SendResult;
|
||||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.support.MessageBuilder;
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
import org.springframework.scripting.support.ResourceScriptSource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -75,6 +79,8 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
.maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
|
.maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
|
||||||
.expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
|
.expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
|
||||||
.build();
|
.build();
|
||||||
|
@Resource
|
||||||
|
private NoteLikeDOService noteLikeDOService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Response<?> publishNote(PublishNoteReqVO publishNoteReqVO) {
|
public Response<?> publishNote(PublishNoteReqVO publishNoteReqVO) {
|
||||||
@@ -552,6 +558,130 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
|
|||||||
return Response.success();
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<?> likeNote(LikeNoteReqVO likeNoteReqVO) {
|
||||||
|
Long noteId = likeNoteReqVO.getId();
|
||||||
|
// 1. 校验被点赞的笔记是否存在
|
||||||
|
checkNoteIsExist(noteId);
|
||||||
|
// 2. 判断目标笔记,是否已经点赞过
|
||||||
|
Long userId = LoginUserContextHolder.getUserId();
|
||||||
|
|
||||||
|
// 布隆过滤器Key
|
||||||
|
String bloomUserNoteLikeListKey = RedisKeyConstants.buildBloomUserNoteLikeListKey(userId);
|
||||||
|
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
||||||
|
// Lua 脚本路径
|
||||||
|
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_note_like_check.lua")));
|
||||||
|
script.setResultType(Long.class);
|
||||||
|
// 执行 Lua 脚本,拿到返回结果
|
||||||
|
Long result = redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), noteId);
|
||||||
|
|
||||||
|
NoteLikeLuaResultEnum noteLikeLuaResultEnum = NoteLikeLuaResultEnum.valueOf(result);
|
||||||
|
|
||||||
|
assert noteLikeLuaResultEnum != null;
|
||||||
|
switch (noteLikeLuaResultEnum) {
|
||||||
|
// Redis 中布隆过滤器不存在
|
||||||
|
case BLOOM_NOT_EXIST -> {
|
||||||
|
// TODO: 从数据库中校验笔记是否被点赞,并异步初始化布隆过滤器,设置过期时间
|
||||||
|
long count = noteLikeDOService.count(new LambdaQueryWrapper<>(NoteLikeDO.class)
|
||||||
|
.eq(NoteLikeDO::getNoteId, noteId)
|
||||||
|
.eq(NoteLikeDO::getStatus, LikeStatusEnum.LIKE.getCode()));
|
||||||
|
|
||||||
|
// 保底1天+随机秒数
|
||||||
|
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
|
||||||
|
|
||||||
|
// 目标笔记已经被点赞
|
||||||
|
if (count > 0) {
|
||||||
|
// 异步初始化布隆过滤器
|
||||||
|
asynBatchAddNoteLike2BloomAndExpire(userId, expireSeconds, bloomUserNoteLikeListKey);
|
||||||
|
throw new ApiException(ResponseCodeEnum.NOTE_ALREADY_LIKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 若数据库中也没有点赞记录,说明该用户还未点赞过任何笔记
|
||||||
|
// Lua 脚本路径
|
||||||
|
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_add_note_like_and_expire.lua")));
|
||||||
|
// 返回值类型
|
||||||
|
script.setResultType(Long.class);
|
||||||
|
redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), noteId, expireSeconds);
|
||||||
|
}
|
||||||
|
// 目标笔记已经被点赞
|
||||||
|
case NOTE_LIKED -> throw new ApiException(ResponseCodeEnum.NOTE_ALREADY_LIKED);
|
||||||
|
}
|
||||||
|
// 3. 更新用户 ZSET 点赞列表
|
||||||
|
|
||||||
|
// 4. 发送 MQ, 将点赞数据落库
|
||||||
|
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步批量添加笔记点赞记录到布隆过滤器中,并设置过期时间
|
||||||
|
*
|
||||||
|
* @param userId 用户 ID
|
||||||
|
* @param expireSeconds 过期时间(秒)
|
||||||
|
* @param bloomUserNoteLikeListKey 布隆过滤器 Key
|
||||||
|
*/
|
||||||
|
private void asynBatchAddNoteLike2BloomAndExpire(Long userId, long expireSeconds, String bloomUserNoteLikeListKey) {
|
||||||
|
threadPoolTaskExecutor.submit(() -> {
|
||||||
|
try {
|
||||||
|
List<NoteLikeDO> noteLikeDOS = noteLikeDOService.list(new LambdaQueryWrapper<>(NoteLikeDO.class)
|
||||||
|
.eq(NoteLikeDO::getUserId, userId));
|
||||||
|
if (CollUtil.isNotEmpty(noteLikeDOS)) {
|
||||||
|
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
|
||||||
|
// Lua 脚本路径
|
||||||
|
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/bloom_batch_add_note_like_and_expire.lua")));
|
||||||
|
// 返回值类型
|
||||||
|
script.setResultType(Long.class);
|
||||||
|
|
||||||
|
// 构建 Lua 参数
|
||||||
|
List<Object> luaArgs = Lists.newArrayList();
|
||||||
|
noteLikeDOS.forEach(noteLikeDO -> luaArgs.add(noteLikeDO.getNoteId())); // 将每个点赞的笔记 ID 传入
|
||||||
|
luaArgs.add(expireSeconds); // 最后一个参数是过期时间(秒)
|
||||||
|
redisTemplate.execute(script, Collections.singletonList(bloomUserNoteLikeListKey), luaArgs.toArray());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("异步批量添加笔记点赞记录到布隆过滤器中,并设置过期时间失败...", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验笔记是否存在
|
||||||
|
*
|
||||||
|
* @param noteId 笔记 ID
|
||||||
|
*/
|
||||||
|
private void checkNoteIsExist(Long noteId) {
|
||||||
|
// 先从本地缓存中检验
|
||||||
|
String findNoteDetailRspVOStrLocalCache = LOCAL_CACHE.getIfPresent(noteId);
|
||||||
|
// 解析 JSON 为 FindNoteDetailRspVO
|
||||||
|
FindNoteDetailRspVO findNoteDetailRspVO = JsonUtils.parseObject(findNoteDetailRspVOStrLocalCache, FindNoteDetailRspVO.class);
|
||||||
|
|
||||||
|
// 若缓存不存在
|
||||||
|
if (Objects.isNull(findNoteDetailRspVO)) {
|
||||||
|
// 从 Redis 中获取
|
||||||
|
String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
|
||||||
|
String noteDetailJson = redisTemplate.opsForValue().get(noteDetailRedisKey);
|
||||||
|
|
||||||
|
// 解析字符串为 FindNoteDetailRspVO
|
||||||
|
findNoteDetailRspVO = JsonUtils.parseObject(noteDetailJson, FindNoteDetailRspVO.class);
|
||||||
|
|
||||||
|
// 若 Redis 中不存在,则从数据库中获取
|
||||||
|
|
||||||
|
if (Objects.isNull(findNoteDetailRspVO)) {
|
||||||
|
boolean isExist = this.exists(new LambdaQueryWrapper<>(NoteDO.class)
|
||||||
|
.eq(NoteDO::getId, noteId)
|
||||||
|
.eq(NoteDO::getStatus, NoteStatusEnum.NORMAL.getCode()));
|
||||||
|
if (!isExist) {
|
||||||
|
throw new ApiException(ResponseCodeEnum.NOTE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
// 缓存
|
||||||
|
threadPoolTaskExecutor.submit(() -> {
|
||||||
|
FindNoteDetailReqVO findNoteDetailReqVO = FindNoteDetailReqVO.builder().id(noteId).build();
|
||||||
|
findNoteDetail(findNoteDetailReqVO);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验笔记的可见性
|
* 校验笔记的可见性
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- 操作的 Key
|
||||||
|
local key = KEYS[1]
|
||||||
|
local noteId = ARGV[1] -- 笔记ID
|
||||||
|
local expireSeconds = ARGV[2] -- 过期时间(秒)
|
||||||
|
|
||||||
|
redis.call("BF.ADD", key, noteId)
|
||||||
|
-- 设置过期时间
|
||||||
|
redis.call("EXPIRE", key, expireSeconds)
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
-- 操作的 Key
|
||||||
|
local key = KEYS[1]
|
||||||
|
|
||||||
|
for i = 1, #ARGV - 1 do
|
||||||
|
redis.call("BF.ADD", key, ARGV[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 最后一个参数为过期时间
|
||||||
|
local expireTime = ARGV[#ARGV]
|
||||||
|
-- 设置过期时间
|
||||||
|
redis.call("EXPIRE", key, expireTime)
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
-- LUA 脚本:点赞布隆过滤器
|
||||||
|
|
||||||
|
local key = KEYS[1] -- 操作的 Redis Key
|
||||||
|
local noteId = ARGV[1] -- 笔记ID
|
||||||
|
|
||||||
|
-- 使用 EXISTS 命令检查布隆过滤器是否存在
|
||||||
|
local exists = redis.call('EXISTS', key)
|
||||||
|
if exists == 0 then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 校验该篇笔记是否被点赞过(1 表示已经点赞,0 表示未点赞)
|
||||||
|
local isLiked = redis.call('BF.EXISTS', key, noteId)
|
||||||
|
if isLiked == 1 then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 未被点赞,添加点赞数据
|
||||||
|
redis.call('BF.ADD', key, noteId)
|
||||||
|
return 0
|
||||||
@@ -184,3 +184,12 @@ Authorization: Bearer {{token}}
|
|||||||
"userId": 100,
|
"userId": 100,
|
||||||
"pageNo": 1
|
"pageNo": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
### 笔记点赞入口
|
||||||
|
POST http://localhost:8000/note/note/like
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer {{token}}
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": {{noteId}}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user