feat(note):优化笔记详情查询性能

- 引入 CompletableFuture 实现 RPC 调用异步化
- 并行调用用户服务与内容服务提升响应速度
- 使用 allOf 统一处理多个异步任务结果
- 保留原有缓存逻辑及异常处理机制
- 调整代码结构提高可读性和维护性
This commit is contained in:
Hanserwei
2025-10-09 11:37:21 +08:00
parent c75b1f6fe4
commit 9772a68ee4

View File

@@ -39,6 +39,7 @@ import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@@ -167,6 +168,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
} }
@Override @Override
@SneakyThrows
public Response<FindNoteDetailRspVO> findNoteDetail(FindNoteDetailReqVO findNoteDetailReqVO) { public Response<FindNoteDetailRspVO> findNoteDetail(FindNoteDetailReqVO findNoteDetailReqVO) {
// 查询笔记ID // 查询笔记ID
Long noteId = findNoteDetailReqVO.getId(); Long noteId = findNoteDetailReqVO.getId();
@@ -208,7 +210,7 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
.eq(NoteDO::getStatus, 1)); .eq(NoteDO::getStatus, 1));
// 若笔记不存在,则抛异常 // 若笔记不存在,则抛异常
if (Objects.isNull(noteDO)){ if (Objects.isNull(noteDO)) {
threadPoolTaskExecutor.execute(() -> { threadPoolTaskExecutor.execute(() -> {
// 防止缓存穿透,将空数据存入 Redis 缓存 (过期时间不宜设置过长) // 防止缓存穿透,将空数据存入 Redis 缓存 (过期时间不宜设置过长)
// 保底1分钟 + 随机秒数 // 保底1分钟 + 随机秒数
@@ -224,47 +226,61 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
// RPC调用用户服务获取用户信息 // RPC调用用户服务获取用户信息
Long creatorId = noteDO.getCreatorId(); Long creatorId = noteDO.getCreatorId();
FindUserByIdRspDTO findUserByIdRspDTO = userRpcService.findById(creatorId); CompletableFuture<FindUserByIdRspDTO> userResultFuture = CompletableFuture
.supplyAsync(() -> userRpcService.findById(creatorId), threadPoolTaskExecutor);
// RPC: 调用 K-V 存储服务获取内容 // RPC: 调用 K-V 存储服务获取内容
String content = null; CompletableFuture<String> contentResultFuture = CompletableFuture.completedFuture(null);
if (Objects.equals(noteDO.getIsContentEmpty(), Boolean.FALSE)) { if (Objects.equals(noteDO.getIsContentEmpty(), Boolean.FALSE)) {
content = keyValueRpcService.findNoteContent(noteDO.getContentUuid()); contentResultFuture = CompletableFuture
.supplyAsync(() -> keyValueRpcService.findNoteContent(noteDO.getContentUuid()), threadPoolTaskExecutor);
} }
CompletableFuture<String> finalContentResultFuture = contentResultFuture;
CompletableFuture<FindNoteDetailRspVO> resultFuture = CompletableFuture
.allOf(userResultFuture, contentResultFuture)
.thenApply(s -> {
// 获取 Future 返回的结果
FindUserByIdRspDTO findUserByIdRspDTO = userResultFuture.join();
String content = finalContentResultFuture.join();
// 笔记类型 // 笔记类型
Integer noteType = noteDO.getType(); Integer noteType = noteDO.getType();
// 图文笔记图片链接(字符串) // 图文笔记图片链接(字符串)
String imgUrisStr = noteDO.getImgUris(); String imgUrisStr = noteDO.getImgUris();
// 图文笔记图片链接(集合) // 图文笔记图片链接(集合)
List<String> imgUris = null; List<String> imgUris = null;
// 如果查询的是图文笔记,需要将图片链接的逗号分隔开,转换成集合 // 如果查询的是图文笔记,需要将图片链接的逗号分隔开,转换成集合
if (Objects.equals(noteType, NoteTypeEnum.IMAGE_TEXT.getCode()) if (Objects.equals(noteType, NoteTypeEnum.IMAGE_TEXT.getCode())
&& StringUtils.isNotBlank(imgUrisStr)) { && StringUtils.isNotBlank(imgUrisStr)) {
imgUris = List.of(imgUrisStr.split(",")); imgUris = List.of(imgUrisStr.split(","));
} }
// 构建返参 VO 实体类 // 构建返参 VO 实体类
FindNoteDetailRspVO findNoteDetailRspVO = FindNoteDetailRspVO.builder() return FindNoteDetailRspVO.builder()
.id(noteDO.getId()) .id(noteDO.getId())
.type(noteDO.getType()) .type(noteDO.getType())
.title(noteDO.getTitle()) .title(noteDO.getTitle())
.content(content) .content(content)
.imgUris(imgUris) .imgUris(imgUris)
.topicId(noteDO.getTopicId()) .topicId(noteDO.getTopicId())
.topicName(noteDO.getTopicName()) .topicName(noteDO.getTopicName())
.creatorId(noteDO.getCreatorId()) .creatorId(noteDO.getCreatorId())
.creatorName(findUserByIdRspDTO.getNickName()) .creatorName(findUserByIdRspDTO.getNickName())
.avatar(findUserByIdRspDTO.getAvatar()) .avatar(findUserByIdRspDTO.getAvatar())
.videoUri(noteDO.getVideoUri()) .videoUri(noteDO.getVideoUri())
.updateTime(noteDO.getUpdateTime()) .updateTime(noteDO.getUpdateTime())
.visible(noteDO.getVisible()) .visible(noteDO.getVisible())
.build(); .build();
});
// 获取拼装后的 FindNoteDetailRspVO
FindNoteDetailRspVO findNoteDetailRspVO = resultFuture.get();
// 异步线程中将笔记详情存入 Redis // 异步线程中将笔记详情存入 Redis
threadPoolTaskExecutor.submit(() -> { threadPoolTaskExecutor.submit(() -> {
String noteDetailJson1 = JsonUtils.toJsonString(findNoteDetailRspVO); String noteDetailJson1 = JsonUtils.toJsonString(findNoteDetailRspVO);
// 过期时间保底1天 + 随机秒数,将缓存过期时间打散,防止同一时间大量缓存失效,导致数据库压力太大) // 过期时间保底1天 + 随机秒数,将缓存过期时间打散,防止同一时间大量缓存失效,导致数据库压力太大)
long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24); long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
redisTemplate.opsForValue().set(noteDetailRedisKey, noteDetailJson1, expireSeconds, TimeUnit.SECONDS); redisTemplate.opsForValue().set(noteDetailRedisKey, noteDetailJson1, expireSeconds, TimeUnit.SECONDS);
}); });
return Response.success(findNoteDetailRspVO); return Response.success(findNoteDetailRspVO);
@@ -272,8 +288,9 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
/** /**
* 校验笔记的可见性 * 校验笔记的可见性
* @param visible 是否可见 *
* @param userId 当前用户 ID * @param visible 是否可见
* @param userId 当前用户 ID
* @param creatorId 笔记创建者 * @param creatorId 笔记创建者
*/ */
private void checkNoteVisible(Integer visible, Long userId, Long creatorId) { private void checkNoteVisible(Integer visible, Long userId, Long creatorId) {
@@ -285,7 +302,8 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
/** /**
* 校验笔记的可见性(针对 VO 实体类) * 校验笔记的可见性(针对 VO 实体类)
* @param userId 当前用户 ID *
* @param userId 当前用户 ID
* @param findNoteDetailRspVO 笔记详情VO类 * @param findNoteDetailRspVO 笔记详情VO类
*/ */
private void checkNoteVisibleFromVO(Long userId, FindNoteDetailRspVO findNoteDetailRspVO) { private void checkNoteVisibleFromVO(Long userId, FindNoteDetailRspVO findNoteDetailRspVO) {