Compare commits

..

7 Commits

Author SHA1 Message Date
Hanserwei
5b19e715ce feat(note): 引入 RocketMQ 实现缓存一致性
- 新增 RocketMQ 配置类,引入自动配置
- 添加 RocketMQ 依赖到 pom.xml 文件
- 定义 MQ 常量接口,包括删除本地缓存和延迟删除 Redis 缓存的主题
- 实现延迟删除 Redis 缓存的消息消费者
- 实现删除本地缓存的广播模式消息消费者
- 在笔记更新服务中集成 RocketMQ,实现延迟双删策略
- 发送异步延时消息用于最终删除 Redis 缓存
- 发送同步广播消息以清除所有实例中的本地缓存
2025-10-09 17:24:51 +08:00
Hanserwei
d00933caad fix(note):优化笔记内容更新逻辑
- 修改查询笔记内容 UUID 的注释表述
- 当笔记内容为空时,删除对应的 K-V 存储
- 若从无内容更新为有内容,重新生成内容 UUID
- 调用 K-V 服务保存或删除笔记内容
- 更新失败时抛出业务异常以回滚事务
2025-10-09 16:21:22 +08:00
Hanserwei
04196f8e3a feat(note): 添加笔记更新功能
- 新增 /update 接口用于修改笔记内容
- 实现笔记类型校验,支持图文和视频笔记
- 校验图片数量不超过8张,视频链接不为空
- 更新笔记元数据并清除Redis和本地缓存
- 支持笔记内容的更新与K-V存储同步
- 新增笔记更新失败和话题不存在的异常码
- 添加 UpdateNoteReqVO 请求参数校验
2025-10-09 12:17:56 +08:00
Hanserwei
7508f176e8 fix(note): 处理笔记保存失败异常
- 在保存笔记失败时抛出 NOTE_PUBLISH_FAIL 异常
- 修复 ResponseCodeEnum 中重复分号问题
2025-10-09 11:38:15 +08:00
Hanserwei
9772a68ee4 feat(note):优化笔记详情查询性能
- 引入 CompletableFuture 实现 RPC 调用异步化
- 并行调用用户服务与内容服务提升响应速度
- 使用 allOf 统一处理多个异步任务结果
- 保留原有缓存逻辑及异常处理机制
- 调整代码结构提高可读性和维护性
2025-10-09 11:37:21 +08:00
Hanserwei
c75b1f6fe4 feat(note): 新增笔记详情查询功能
- 新增笔记详情请求VO类 FindNoteDetailReqVO
- 新增笔记详情响应VO类 FindNoteDetailRspVO
- KV服务Feign接口新增查询笔记内容方法
- KeyValueRpcService新增findNoteContent方法实现
- NoteController新增笔记详情查询接口
- NoteService接口及实现类新增findNoteDetail方法
- 新增RedisKeyConstants常量类用于构建笔记详情缓存KEY
- 新增ResponseCodeEnum枚举值用于笔记相关异常码
- 新增ThreadPoolConfig配置类定义异步线程池
- 新增UserRpcService用于调用用户服务查询用户信息
- 笔记详情接口支持多级缓存(本地缓存Caffeine+Redis)
- 笔记详情查询增加可见性校验逻辑
- pom.xml新增用户服务api依赖和Caffeine依赖
- UserFeignApi新增根据ID查询用户信息接口
2025-10-09 11:30:59 +08:00
Hanserwei
869889b87d 子模块打包文件 2025-10-09 10:36:39 +08:00
28 changed files with 1031 additions and 6 deletions

View File

@@ -0,0 +1,37 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父项目 -->
<parent>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-distributed-id-generator</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 打包方式 -->
<packaging>jar</packaging>
<artifactId>han-note-distributed-id-generator-api</artifactId>
<name>${project.artifactId}</name>
<description>RPC层, 供其他服务调用</description>
<dependencies>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-common</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,26 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父项目 -->
<parent>
<groupId>com.hanserwei</groupId>
<artifactId>han-note</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 多模块项目需要配置打包方式为 pom -->
<packaging>pom</packaging>
<!-- 子模块管理 -->
<modules>
<module>han-note-kv-api</module>
<module>han-note-kv-biz</module>
</modules>
<artifactId>han-note-kv</artifactId>
<!-- 项目名称 -->
<name>${project.artifactId}</name>
<!-- 项目描述 -->
<description>Key-Value 键值存储服务</description>
</project>

View File

@@ -0,0 +1,36 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父项目 -->
<parent>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-kv</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 打包方式 -->
<packaging>jar</packaging>
<artifactId>han-note-kv-api</artifactId>
<name>${project.artifactId}</name>
<description>RPC层, 供其他服务调用</description>
<dependencies>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-common</artifactId>
</dependency>
<!-- OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -4,6 +4,8 @@ import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.kv.constant.ApiConstants; import com.hanserwei.hannote.kv.constant.ApiConstants;
import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO; import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO; import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.req.FindNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.resp.FindNoteContentRspDTO;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -17,7 +19,7 @@ public interface KeyValueFeignApi {
Response<?> addNoteContent(@RequestBody AddNoteContentReqDTO addNoteContentReqDTO); Response<?> addNoteContent(@RequestBody AddNoteContentReqDTO addNoteContentReqDTO);
@PostMapping(value = PREFIX + "/note/content/find") @PostMapping(value = PREFIX + "/note/content/find")
Response<?> findNoteContent(@RequestBody AddNoteContentReqDTO addNoteContentReqDTO); Response<FindNoteContentRspDTO> findNoteContent(@RequestBody FindNoteContentReqDTO findNoteContentReqDTO);
@PostMapping(value = PREFIX + "/note/content/delete") @PostMapping(value = PREFIX + "/note/content/delete")
Response<?> deleteNoteContent(@RequestBody DeleteNoteContentReqDTO deleteNoteContentReqDTO); Response<?> deleteNoteContent(@RequestBody DeleteNoteContentReqDTO deleteNoteContentReqDTO);

View File

@@ -0,0 +1,57 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父项目 -->
<parent>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-kv</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 打包方式 -->
<packaging>jar</packaging>
<artifactId>han-note-kv-biz</artifactId>
<name>${project.artifactId}</name>
<description>Key-Value 键值存储业务层</description>
<dependencies>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Cassandra 存储 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-cassandra</artifactId>
</dependency>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-kv-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,24 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父项目 -->
<parent>
<groupId>com.hanserwei</groupId>
<artifactId>han-note</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 多模块项目需要配置打包方式为 pom -->
<packaging>pom</packaging>
<!-- 子模块管理 -->
<modules>
<module>han-note-note-api</module>
</modules>
<artifactId>han-note-note</artifactId>
<!-- 项目名称 -->
<name>${project.artifactId}</name>
<!-- 项目描述 -->
<description>笔记服务</description>
</project>

View File

@@ -0,0 +1,24 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父项目 -->
<parent>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-note</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 打包方式 -->
<packaging>jar</packaging>
<artifactId>han-note-api</artifactId>
<name>${project.artifactId}</name>
<description>RPC层, 供其他服务调用</description>
<dependencies>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,114 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父项目 -->
<parent>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-note</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<!-- 打包方式 -->
<packaging>jar</packaging>
<artifactId>han-note-note-biz</artifactId>
<name>${project.artifactId}</name>
<description>笔记服务业务层</description>
<dependencies>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-common</artifactId>
</dependency>
<!-- 业务接口日志组件 -->
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-spring-boot-starter-biz-operationlog</artifactId>
</dependency>
<!-- 上下文组件 -->
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-spring-boot-starter-biz-context</artifactId>
</dependency>
<!-- Jackson 组件 -->
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>hanserwei-spring-boot-starter-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- Druid 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- 服务发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-kv-api</artifactId>
</dependency>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-distributed-id-generator-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@@ -100,6 +100,24 @@
<groupId>com.hanserwei</groupId> <groupId>com.hanserwei</groupId>
<artifactId>han-note-distributed-id-generator-api</artifactId> <artifactId>han-note-distributed-id-generator-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-user-api</artifactId>
</dependency>
<!-- Caffeine 本地缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- Rocket MQ -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
</dependency>
</dependencies> </dependencies>

View File

@@ -0,0 +1,31 @@
package com.hanserwei.hannote.note.biz.comsumer;
import com.hanserwei.hannote.note.biz.constant.MQConstants;
import com.hanserwei.hannote.note.biz.constant.RedisKeyConstants;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RocketMQMessageListener(consumerGroup = "han_note_group_" + MQConstants.TOPIC_DELAY_DELETE_NOTE_REDIS_CACHE, // Group
topic = MQConstants.TOPIC_DELAY_DELETE_NOTE_REDIS_CACHE // 消费的主题 Topic
)
public class DelayDeleteNoteRedisCacheConsumer implements RocketMQListener<String> {
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public void onMessage(String body) {
Long noteId = Long.valueOf(body);
log.info("## 延迟消息消费成功, noteId: {}", noteId);
// 删除 Redis 笔记缓存
String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
redisTemplate.delete(noteDetailRedisKey);
}
}

View File

@@ -0,0 +1,23 @@
package com.hanserwei.hannote.note.biz.comsumer;
import com.hanserwei.hannote.note.biz.constant.MQConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RocketMQMessageListener(
consumerGroup = "han_note_group",
topic = MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE,
messageModel = MessageModel.BROADCASTING
)
public class DeleteNoteLocalCacheConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String body) {
Long noteId = Long.valueOf(body);
log.info("## 消费者消费成功, noteId: {}", noteId);
}
}

View File

@@ -0,0 +1,10 @@
package com.hanserwei.hannote.note.biz.config;
import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@Import(RocketMQAutoConfiguration.class)
public class RocketMQConfig {
}

View File

@@ -0,0 +1,37 @@
package com.hanserwei.hannote.note.biz.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
@Bean(name = "noteTaskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(10);
// 最大线程数
executor.setMaxPoolSize(50);
// 队列容量
executor.setQueueCapacity(200);
// 线程活跃时间(秒)
executor.setKeepAliveSeconds(30);
// 线程名前缀
executor.setThreadNamePrefix("NoteExecutor-");
// 拒绝策略:由调用线程处理(一般为主线程)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是被没有完成的任务阻塞
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}

View File

@@ -0,0 +1,14 @@
package com.hanserwei.hannote.note.biz.constant;
public interface MQConstants {
/**
* Topic 主题:删除笔记的本地缓存
*/
String TOPIC_DELETE_NOTE_LOCAL_CACHE = "DeleteNoteLocalCacheTopic";
/**
* Topic 主题:延迟双删 Redis 笔记缓存
*/
String TOPIC_DELAY_DELETE_NOTE_REDIS_CACHE = "DelayDeleteNoteRedisCacheTopic";
}

View File

@@ -0,0 +1,20 @@
package com.hanserwei.hannote.note.biz.constant;
public class RedisKeyConstants {
/**
* 笔记详情 KEY 前缀
*/
public static final String NOTE_DETAIL_KEY = "note:detail:";
/**
* 构建完整的笔记详情 KEY
* @param noteId 笔记ID
* @return 笔记详情 KEY
*/
public static String buildNoteDetailKey(Long noteId) {
return NOTE_DETAIL_KEY + noteId;
}
}

View File

@@ -2,7 +2,10 @@ package com.hanserwei.hannote.note.biz.controller;
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog; import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
import com.hanserwei.framework.common.response.Response; import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.note.biz.model.vo.FindNoteDetailReqVO;
import com.hanserwei.hannote.note.biz.model.vo.FindNoteDetailRspVO;
import com.hanserwei.hannote.note.biz.model.vo.PublishNoteReqVO; import com.hanserwei.hannote.note.biz.model.vo.PublishNoteReqVO;
import com.hanserwei.hannote.note.biz.model.vo.UpdateNoteReqVO;
import com.hanserwei.hannote.note.biz.service.NoteService; import com.hanserwei.hannote.note.biz.service.NoteService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -26,4 +29,16 @@ public class NoteController {
return noteService.publishNote(publishNoteReqVO); return noteService.publishNote(publishNoteReqVO);
} }
@PostMapping(value = "/detail")
@ApiOperationLog(description = "笔记详情")
public Response<FindNoteDetailRspVO> findNoteDetail(@Validated @RequestBody FindNoteDetailReqVO findNoteDetailReqVO) {
return noteService.findNoteDetail(findNoteDetailReqVO);
}
@PostMapping(value = "/update")
@ApiOperationLog(description = "笔记修改")
public Response<?> updateNote(@Validated @RequestBody UpdateNoteReqVO updateNoteReqVO) {
return noteService.updateNote(updateNoteReqVO);
}
} }

View File

@@ -1,9 +1,6 @@
package com.hanserwei.hannote.note.biz.domain.dataobject; package com.hanserwei.hannote.note.biz.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;

View File

@@ -15,6 +15,10 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
// ----------- 业务异常状态码 ----------- // ----------- 业务异常状态码 -----------
NOTE_TYPE_ERROR("NOTE-20000", "未知的笔记类型"), NOTE_TYPE_ERROR("NOTE-20000", "未知的笔记类型"),
NOTE_PUBLISH_FAIL("NOTE-20001", "笔记发布失败"), NOTE_PUBLISH_FAIL("NOTE-20001", "笔记发布失败"),
NOTE_NOT_FOUND("NOTE-20002", "笔记不存在"),
NOTE_PRIVATE("NOTE-20003", "作者已将该笔记设置为仅自己可见"),
NOTE_UPDATE_FAIL("NOTE-20004", "笔记更新失败"),
TOPIC_NOT_FOUND("NOTE-20005", "话题不存在")
; ;
// 异常码 // 异常码

View File

@@ -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 FindNoteDetailReqVO {
@NotNull(message = "笔记 ID 不能为空")
private Long id;
}

View File

@@ -0,0 +1,49 @@
package com.hanserwei.hannote.note.biz.model.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FindNoteDetailRspVO {
private Long id;
private Integer type;
private String title;
private String content;
private List<String> imgUris;
private Long topicId;
private String topicName;
private Long creatorId;
private String creatorName;
private String avatar;
private String videoUri;
/**
* 编辑时间
*/
private LocalDateTime updateTime;
/**
* 是否可见
*/
private Integer visible;
}

View File

@@ -0,0 +1,32 @@
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;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UpdateNoteReqVO {
@NotNull(message = "笔记 ID 不能为空")
private Long id;
@NotNull(message = "笔记类型不能为空")
private Integer type;
private List<String> imgUris;
private String videoUri;
private String title;
private String content;
private Long topicId;
}

View File

@@ -4,6 +4,8 @@ import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.kv.api.KeyValueFeignApi; import com.hanserwei.hannote.kv.api.KeyValueFeignApi;
import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO; import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO; import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.req.FindNoteContentReqDTO;
import com.hanserwei.hannote.kv.dto.resp.FindNoteContentRspDTO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -47,4 +49,23 @@ public class KeyValueRpcService {
return Objects.nonNull(response) && response.isSuccess(); return Objects.nonNull(response) && response.isSuccess();
} }
/**
* 查询笔记内容
*
* @param uuid 笔记UUID
* @return 笔记内容
*/
public String findNoteContent(String uuid) {
FindNoteContentReqDTO findNoteContentReqDTO = new FindNoteContentReqDTO();
findNoteContentReqDTO.setUuid(uuid);
Response<FindNoteContentRspDTO> response = keyValueFeignApi.findNoteContent(findNoteContentReqDTO);
if (Objects.isNull(response) || !response.isSuccess() || Objects.isNull(response.getData())) {
return null;
}
return response.getData().getContent();
}
} }

View File

@@ -0,0 +1,37 @@
package com.hanserwei.hannote.note.biz.rpc;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.user.api.UserFeignApi;
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Component
public class UserRpcService {
@Resource
private UserFeignApi userFeignApi;
/**
* 查询用户信息
*
* @param userId 用户ID
* @return 用户信息
*/
public FindUserByIdRspDTO findById(Long userId) {
FindUserByIdReqDTO findUserByIdReqDTO = new FindUserByIdReqDTO();
findUserByIdReqDTO.setId(userId);
Response<FindUserByIdRspDTO> response = userFeignApi.findById(findUserByIdReqDTO);
if (Objects.isNull(response) || !response.isSuccess()) {
return null;
}
return response.getData();
}
}

View File

@@ -3,7 +3,10 @@ package com.hanserwei.hannote.note.biz.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.hanserwei.framework.common.response.Response; import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.note.biz.domain.dataobject.NoteDO; import com.hanserwei.hannote.note.biz.domain.dataobject.NoteDO;
import com.hanserwei.hannote.note.biz.model.vo.FindNoteDetailReqVO;
import com.hanserwei.hannote.note.biz.model.vo.FindNoteDetailRspVO;
import com.hanserwei.hannote.note.biz.model.vo.PublishNoteReqVO; import com.hanserwei.hannote.note.biz.model.vo.PublishNoteReqVO;
import com.hanserwei.hannote.note.biz.model.vo.UpdateNoteReqVO;
public interface NoteService extends IService<NoteDO> { public interface NoteService extends IService<NoteDO> {
@@ -14,4 +17,18 @@ public interface NoteService extends IService<NoteDO> {
*/ */
Response<?> publishNote(PublishNoteReqVO publishNoteReqVO); Response<?> publishNote(PublishNoteReqVO publishNoteReqVO);
/**
* 笔记详情
* @param findNoteDetailReqVO 笔记详情请求
* @return 笔记详情结果
*/
Response<FindNoteDetailRspVO> findNoteDetail(FindNoteDetailReqVO findNoteDetailReqVO);
/**
* 笔记更新
* @param updateNoteReqVO 笔记更新请求
* @return 笔记更新结果
*/
Response<?> updateNote(UpdateNoteReqVO updateNoteReqVO);
} }

View File

@@ -1,31 +1,55 @@
package com.hanserwei.hannote.note.biz.service.impl; package com.hanserwei.hannote.note.biz.service.impl;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
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;
import com.hanserwei.framework.common.utils.JsonUtils;
import com.hanserwei.hannote.note.biz.constant.MQConstants;
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.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.NoteStatusEnum;
import com.hanserwei.hannote.note.biz.enums.NoteTypeEnum; import com.hanserwei.hannote.note.biz.enums.NoteTypeEnum;
import com.hanserwei.hannote.note.biz.enums.NoteVisibleEnum; import com.hanserwei.hannote.note.biz.enums.NoteVisibleEnum;
import com.hanserwei.hannote.note.biz.enums.ResponseCodeEnum; import com.hanserwei.hannote.note.biz.enums.ResponseCodeEnum;
import com.hanserwei.hannote.note.biz.model.vo.FindNoteDetailReqVO;
import com.hanserwei.hannote.note.biz.model.vo.FindNoteDetailRspVO;
import com.hanserwei.hannote.note.biz.model.vo.PublishNoteReqVO; import com.hanserwei.hannote.note.biz.model.vo.PublishNoteReqVO;
import com.hanserwei.hannote.note.biz.model.vo.UpdateNoteReqVO;
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.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 jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; 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;
@Slf4j @Slf4j
@Service @Service
@@ -36,6 +60,24 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
private KeyValueRpcService keyValueRpcService; private KeyValueRpcService keyValueRpcService;
@Resource @Resource
private TopicDOService topicDOService; private TopicDOService topicDOService;
@Resource
private UserRpcService userRpcService;
@Resource(name = "noteTaskExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Resource
private RedisTemplate<String, String> redisTemplate;
@Resource
private RocketMQTemplate rocketMQTemplate;
/**
* 笔记详情本地缓存
*/
@SuppressWarnings("NullableProblems")
private static final Cache<Long, String> LOCAL_CACHE = Caffeine.newBuilder()
.initialCapacity(10000) // 设置初始容量为 10000 个条目
.maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
.expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
.build();
@Override @Override
public Response<?> publishNote(PublishNoteReqVO publishNoteReqVO) { public Response<?> publishNote(PublishNoteReqVO publishNoteReqVO) {
@@ -123,13 +165,296 @@ public class NoteServiceImpl extends ServiceImpl<NoteDOMapper, NoteDO> implement
.build(); .build();
try { try {
boolean isSaveSuccess = this.save(noteDO); boolean isSaveSuccess = this.save(noteDO);
if (!isSaveSuccess) {
throw new ApiException(ResponseCodeEnum.NOTE_PUBLISH_FAIL);
}
} catch (Exception e) { } catch (Exception e) {
log.error("保存笔记失败!", e); log.error("保存笔记失败!", e);
// RPC调用KV服务删除笔记内容 // RPC调用KV服务删除笔记内容
if (StringUtils.isNotBlank(contentUuid)) { if (StringUtils.isNotBlank(contentUuid)) {
keyValueRpcService.deleteNoteContent(contentUuid); boolean res = keyValueRpcService.deleteNoteContent(contentUuid);
if (!res) {
log.error("删除笔记内容失败!");
}
} }
} }
return Response.success(); return Response.success();
} }
@Override
@SneakyThrows
public Response<FindNoteDetailRspVO> findNoteDetail(FindNoteDetailReqVO findNoteDetailReqVO) {
// 查询笔记ID
Long noteId = findNoteDetailReqVO.getId();
// 当前登录用户
Long userId = LoginUserContextHolder.getUserId();
// 先从本地缓存中查询
String findNoteDetailRspVOStrLocalCache = LOCAL_CACHE.getIfPresent(noteId);
if (StringUtils.isNotBlank(findNoteDetailRspVOStrLocalCache)) {
FindNoteDetailRspVO findNoteDetailRspVO = JsonUtils.parseObject(findNoteDetailRspVOStrLocalCache, FindNoteDetailRspVO.class);
log.info("==> 笔记详情命中了本地缓存;{}", findNoteDetailRspVOStrLocalCache);
// 可见性校验
checkNoteVisibleFromVO(userId, findNoteDetailRspVO);
return Response.success(findNoteDetailRspVO);
}
// 从Redis缓存中获取
String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
String noteDetailJson = redisTemplate.opsForValue().get(noteDetailRedisKey);
// 若缓存中有该笔记的数据,则直接返回
if (StringUtils.isNotBlank(noteDetailJson)) {
FindNoteDetailRspVO findNoteDetailRspVO = JsonUtils.parseObject(noteDetailJson, FindNoteDetailRspVO.class);
// 异步线程中将用户信息存入本地缓存
threadPoolTaskExecutor.submit(() -> {
// 写入本地缓存
LOCAL_CACHE.put(noteId,
Objects.isNull(findNoteDetailRspVO) ? "null" : JsonUtils.toJsonString(findNoteDetailRspVO));
});
// 可见性校验
checkNoteVisibleFromVO(userId, findNoteDetailRspVO);
return Response.success(findNoteDetailRspVO);
}
// 查询笔记
NoteDO noteDO = this.getOne(new LambdaQueryWrapper<>(NoteDO.class)
.eq(NoteDO::getId, noteId)
.eq(NoteDO::getStatus, 1));
// 若笔记不存在,则抛异常
if (Objects.isNull(noteDO)) {
threadPoolTaskExecutor.execute(() -> {
// 防止缓存穿透,将空数据存入 Redis 缓存 (过期时间不宜设置过长)
// 保底1分钟 + 随机秒数
long expireSeconds = 60 + RandomUtil.randomInt(60);
redisTemplate.opsForValue().set(noteDetailRedisKey, "null", expireSeconds, TimeUnit.SECONDS);
});
throw new ApiException(ResponseCodeEnum.NOTE_NOT_FOUND);
}
// 可见性校验
Integer visible = noteDO.getVisible();
checkNoteVisible(visible, userId, noteDO.getCreatorId());
// RPC调用用户服务获取用户信息
Long creatorId = noteDO.getCreatorId();
CompletableFuture<FindUserByIdRspDTO> userResultFuture = CompletableFuture
.supplyAsync(() -> userRpcService.findById(creatorId), threadPoolTaskExecutor);
// RPC: 调用 K-V 存储服务获取内容
CompletableFuture<String> contentResultFuture = CompletableFuture.completedFuture(null);
if (Objects.equals(noteDO.getIsContentEmpty(), Boolean.FALSE)) {
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();
// 图文笔记图片链接(字符串)
String imgUrisStr = noteDO.getImgUris();
// 图文笔记图片链接(集合)
List<String> imgUris = null;
// 如果查询的是图文笔记,需要将图片链接的逗号分隔开,转换成集合
if (Objects.equals(noteType, NoteTypeEnum.IMAGE_TEXT.getCode())
&& StringUtils.isNotBlank(imgUrisStr)) {
imgUris = List.of(imgUrisStr.split(","));
}
// 构建返参 VO 实体类
return FindNoteDetailRspVO.builder()
.id(noteDO.getId())
.type(noteDO.getType())
.title(noteDO.getTitle())
.content(content)
.imgUris(imgUris)
.topicId(noteDO.getTopicId())
.topicName(noteDO.getTopicName())
.creatorId(noteDO.getCreatorId())
.creatorName(findUserByIdRspDTO.getNickName())
.avatar(findUserByIdRspDTO.getAvatar())
.videoUri(noteDO.getVideoUri())
.updateTime(noteDO.getUpdateTime())
.visible(noteDO.getVisible())
.build();
});
// 获取拼装后的 FindNoteDetailRspVO
FindNoteDetailRspVO findNoteDetailRspVO = resultFuture.get();
// 异步线程中将笔记详情存入 Redis
threadPoolTaskExecutor.submit(() -> {
String noteDetailJson1 = JsonUtils.toJsonString(findNoteDetailRspVO);
// 过期时间保底1天 + 随机秒数,将缓存过期时间打散,防止同一时间大量缓存失效,导致数据库压力太大)
long expireSeconds = 60 * 60 * 24 + RandomUtil.randomInt(60 * 60 * 24);
redisTemplate.opsForValue().set(noteDetailRedisKey, noteDetailJson1, expireSeconds, TimeUnit.SECONDS);
});
return Response.success(findNoteDetailRspVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Response<?> updateNote(UpdateNoteReqVO updateNoteReqVO) {
// 笔记ID
Long noteId = updateNoteReqVO.getId();
// 笔记类型
Integer type = updateNoteReqVO.getType();
// 获取对应枚举类
NoteTypeEnum noteTypeEnum = NoteTypeEnum.valueOf(type);
// 判断笔记类型,如果非图文、视频笔记,则抛出异常
if (Objects.isNull(noteTypeEnum)){
throw new ApiException(ResponseCodeEnum.NOTE_TYPE_ERROR);
}
String imgUris = null;
String videoUri = null;
switch (noteTypeEnum) {
case IMAGE_TEXT -> {
List<String> imgUriList = updateNoteReqVO.getImgUris();
// 校验图片是否为空
Preconditions.checkArgument(CollUtil.isNotEmpty(imgUriList), "笔记图片不能为空");
// 校验图片数量
Preconditions.checkArgument(imgUriList.size() <= 8, "笔记图片不能多于 8 张");
imgUris = StringUtils.join(imgUriList, ",");
}
case VIDEO -> {
videoUri = updateNoteReqVO.getVideoUri();
// 校验视频链接是否为空
Preconditions.checkArgument(StringUtils.isNotBlank(videoUri), "笔记视频不能为空");
}
default -> {
// No operation needed, kept for clarity
}
}
// 话题
Long topicId = updateNoteReqVO.getTopicId();
String topicName = null;
if (Objects.nonNull(topicId)){
TopicDO topicDO = topicDOService.getById(topicId);
if (Objects.isNull(topicDO)){
throw new ApiException(ResponseCodeEnum.TOPIC_NOT_FOUND);
}
topicName = topicDO.getName();
// 判断提交的话题是否真实存在
if (StringUtils.isBlank(topicName)){
throw new ApiException(ResponseCodeEnum.TOPIC_NOT_FOUND);
}
}
// 删除 Redis 缓存
String noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
redisTemplate.delete(noteDetailRedisKey);
// 更新笔记元数据表
String content = updateNoteReqVO.getContent();
NoteDO noteDO = NoteDO.builder()
.id(noteId)
.isContentEmpty(StringUtils.isBlank(content))
.imgUris(imgUris)
.title(updateNoteReqVO.getTitle())
.topicId(updateNoteReqVO.getTopicId())
.topicName(topicName)
.type(type)
.updateTime(LocalDateTime.now())
.videoUri(videoUri)
.build();
boolean updateResult = this.updateById(noteDO);
if (!updateResult){
throw new ApiException(ResponseCodeEnum.NOTE_UPDATE_FAIL);
}
// 一致性保证:延迟双删策略
// 异步发送延时消息
Message<String> message = MessageBuilder.withPayload(String.valueOf(noteId))
.build();
rocketMQTemplate.asyncSend(MQConstants.TOPIC_DELAY_DELETE_NOTE_REDIS_CACHE, message,
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("## 延时删除 Redis 笔记缓存消息发送成功...");
}
@Override
public void onException(Throwable e) {
log.error("## 延时删除 Redis 笔记缓存消息发送失败...", e);
}
},
3000, // 超时时间(毫秒)
1 // 延迟级别1 表示延时 1s
);
// 删除Redis缓存
noteDetailRedisKey = RedisKeyConstants.buildNoteDetailKey(noteId);
redisTemplate.delete(noteDetailRedisKey);
// 删除本地缓存
LOCAL_CACHE.invalidate(noteId);
// 同步发送广播模式 MQ将所有实例中的本地缓存都删除掉
rocketMQTemplate.syncSend(MQConstants.TOPIC_DELETE_NOTE_LOCAL_CACHE, noteId);
log.info("====> MQ删除笔记本地缓存发送成功...");
// 笔记内容更新
// 查询此篇笔记内容对应的 UUID
NoteDO noteDO1 = this.getById(noteId);
String contentUuid = noteDO1.getContentUuid();
// 笔记内容是否更新成功
boolean isUpdateContentSuccess = false;
if (StringUtils.isBlank(content)) {
// 若笔记内容为空,则删除 K-V 存储
isUpdateContentSuccess = keyValueRpcService.deleteNoteContent(contentUuid);
} else {
// 若将无内容的笔记,更新为了有内容的笔记,需要重新生成 UUID
contentUuid = StringUtils.isBlank(contentUuid) ? UUID.randomUUID().toString() : contentUuid;
// 调用 K-V 更新短文本
isUpdateContentSuccess = keyValueRpcService.saveNoteContent(contentUuid, content);
}
// 如果更新失败,抛出业务异常,回滚事务
if (!isUpdateContentSuccess) {
throw new ApiException(ResponseCodeEnum.NOTE_UPDATE_FAIL);
}
return Response.success();
}
/**
* 校验笔记的可见性
*
* @param visible 是否可见
* @param userId 当前用户 ID
* @param creatorId 笔记创建者
*/
private void checkNoteVisible(Integer visible, Long userId, Long creatorId) {
if (Objects.equals(visible, NoteVisibleEnum.PRIVATE.getCode())
&& !Objects.equals(userId, creatorId)) { // 仅自己可见, 并且访问用户为笔记创建者才能访问,非本人则抛出异常
throw new ApiException(ResponseCodeEnum.NOTE_PRIVATE);
}
}
/**
* 校验笔记的可见性(针对 VO 实体类)
*
* @param userId 当前用户 ID
* @param findNoteDetailRspVO 笔记详情VO类
*/
private void checkNoteVisibleFromVO(Long userId, FindNoteDetailRspVO findNoteDetailRspVO) {
if (Objects.nonNull(findNoteDetailRspVO)) {
Integer visible = findNoteDetailRspVO.getVisible();
checkNoteVisible(visible, userId, findNoteDetailRspVO.getCreatorId());
}
}
} }

View File

@@ -3,9 +3,11 @@ package com.hanserwei.hannote.user.api;
import com.hanserwei.framework.common.response.Response; import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.user.constant.ApiConstants; import com.hanserwei.hannote.user.constant.ApiConstants;
import com.hanserwei.hannote.user.dto.req.FindUserByEmailReqDTO; import com.hanserwei.hannote.user.dto.req.FindUserByEmailReqDTO;
import com.hanserwei.hannote.user.dto.req.FindUserByIdReqDTO;
import com.hanserwei.hannote.user.dto.req.RegisterUserReqDTO; import com.hanserwei.hannote.user.dto.req.RegisterUserReqDTO;
import com.hanserwei.hannote.user.dto.req.UpdateUserPasswordReqDTO; import com.hanserwei.hannote.user.dto.req.UpdateUserPasswordReqDTO;
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO; import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
import com.hanserwei.hannote.user.dto.resp.FindUserByIdRspDTO;
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -42,4 +44,12 @@ public interface UserFeignApi {
@PostMapping(value = PREFIX + "/password/update") @PostMapping(value = PREFIX + "/password/update")
Response<?> updatePassword(@RequestBody UpdateUserPasswordReqDTO updateUserPasswordReqDTO); Response<?> updatePassword(@RequestBody UpdateUserPasswordReqDTO updateUserPasswordReqDTO);
/**
* 根据用户 ID 查询用户信息
*
* @param findUserByIdReqDTO 查询信息请求
* @return 响应
*/
@PostMapping(value = PREFIX + "/findById")
Response<FindUserByIdRspDTO> findById(@RequestBody FindUserByIdReqDTO findUserByIdReqDTO);
} }

View File

@@ -84,6 +84,15 @@
<groupId>com.hanserwei</groupId> <groupId>com.hanserwei</groupId>
<artifactId>han-note-user-api</artifactId> <artifactId>han-note-user-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.hanserwei</groupId>
<artifactId>han-note-distributed-id-generator-api</artifactId>
</dependency>
<!-- Caffeine 本地缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies> </dependencies>

18
pom.xml
View File

@@ -60,6 +60,8 @@
<perf4j.version>0.9.16</perf4j.version> <perf4j.version>0.9.16</perf4j.version>
<curator-recipes.version>5.9.0</curator-recipes.version> <curator-recipes.version>5.9.0</curator-recipes.version>
<zookeeper.version>3.9.4</zookeeper.version> <zookeeper.version>3.9.4</zookeeper.version>
<rocketmq-spring-boot.version>2.3.4</rocketmq-spring-boot.version>
<rocketmq-client.version>5.3.2</rocketmq-client.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
@@ -265,6 +267,22 @@
<artifactId>caffeine</artifactId> <artifactId>caffeine</artifactId>
<version>${caffeine.version}</version> <version>${caffeine.version}</version>
</dependency> </dependency>
<!-- Rocket MQ -->
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>${rocketmq-spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>${rocketmq-client.version}</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-acl</artifactId>
<version>${rocketmq-client.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>