feat(kv): 初始化 KV 服务模块
- 添加了笔记内容的增删查 DTO 类 - 配置了 Cassandra 数据库连接 - 实现了基于 Cassandra 的笔记内容存储与查询功能 feat(kv): 初始化 distributeID 服务模块 - 实现了分布式 ID 生成器服务(Snowflake与 Segment) - 添加了 ID 生成器监控接口 - 配置了 MyBatis 与数据库交互 - 添加了 Segment 与 Snowflake 服务实现 - 添加了 Leaf 相关模型类与分配器接口 - 添加了 Leaf 分配器实现类 - 添加了 Leaf 控制器与监控视图 - 添加了 Leaf 异常处理类 - 添加了 Leaf 日志配置文件 - 添加了 Leaf 启动类 - 添加了 Leaf 常量定义 - 添加了 Leaf ID 生成接口 - 添加了 Leaf 初始化异常类 - 添加了 Leaf 配置文件 - 添加了 Leaf 模型类 - 添加了 Leaf 服务类 - 添加了 Leaf 工具类 - 添加了 Leaf 相关注解 - 添加了 Leaf 相关配置类 - 添加了 Leaf 相关枚举类 - 添加了 Leaf 相关工具类 后续考虑复刻Leaf代码至Java21平台
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
package com.hanserwei.hannote.kv.biz;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class HannoteKVBizApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HannoteKVBizApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.hanserwei.hannote.kv.biz.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.cassandra.config.AbstractCassandraConfiguration;
|
||||
|
||||
@Configuration
|
||||
public class CassandraConfig extends AbstractCassandraConfiguration {
|
||||
|
||||
@Value("${spring.cassandra.keyspace-name}")
|
||||
private String keySpace;
|
||||
|
||||
@Value("${spring.cassandra.contact-points}")
|
||||
private String contactPoints;
|
||||
|
||||
@Value("${spring.cassandra.port}")
|
||||
private int port;
|
||||
@Override
|
||||
protected String getKeyspaceName() {
|
||||
return keySpace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContactPoints() {
|
||||
return contactPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.hanserwei.hannote.kv.biz.controller;
|
||||
|
||||
import com.hanserwei.framework.common.response.Response;
|
||||
import com.hanserwei.hannote.kv.biz.service.NoteContentService;
|
||||
import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO;
|
||||
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 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;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/kv")
|
||||
@Slf4j
|
||||
public class NoteContentController {
|
||||
|
||||
@Resource
|
||||
private NoteContentService noteContentService;
|
||||
|
||||
@PostMapping(value = "/note/content/add")
|
||||
public Response<?> addNoteContent(@Validated @RequestBody AddNoteContentReqDTO addNoteContentReqDTO) {
|
||||
return noteContentService.addNoteContent(addNoteContentReqDTO);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/note/content/find")
|
||||
public Response<FindNoteContentRspDTO> findNoteContent(@Validated @RequestBody FindNoteContentReqDTO findNoteContentReqDTO) {
|
||||
return noteContentService.findNoteContent(findNoteContentReqDTO);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/note/content/delete")
|
||||
public Response<?> deleteNoteContent(@Validated @RequestBody DeleteNoteContentReqDTO deleteNoteContentReqDTO) {
|
||||
return noteContentService.deleteNoteContent(deleteNoteContentReqDTO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.hanserwei.hannote.kv.biz.domain.dataobject;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
|
||||
import org.springframework.data.cassandra.core.mapping.Table;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Table("note_content")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class NoteContentDO {
|
||||
|
||||
@PrimaryKey("id")
|
||||
private UUID id;
|
||||
|
||||
private String content;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.hanserwei.hannote.kv.biz.domain.repository;
|
||||
|
||||
import com.hanserwei.hannote.kv.biz.domain.dataobject.NoteContentDO;
|
||||
import org.springframework.data.cassandra.repository.CassandraRepository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface NoteContentRepository extends CassandraRepository<NoteContentDO, UUID> {
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.hanserwei.hannote.kv.biz.enums;
|
||||
|
||||
import com.hanserwei.framework.common.exception.BaseExceptionInterface;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ResponseCodeEnum implements BaseExceptionInterface {
|
||||
|
||||
// ----------- 通用异常状态码 -----------
|
||||
SYSTEM_ERROR("KV-10000", "出错啦,后台小哥正在努力修复中..."),
|
||||
PARAM_NOT_VALID("KV-10001", "参数错误"),
|
||||
|
||||
// ----------- 业务异常状态码 -----------
|
||||
NOTE_CONTENT_NOT_FOUND("KV-20000", "该笔记内容不存在"),
|
||||
;
|
||||
|
||||
// 异常码
|
||||
private final String errorCode;
|
||||
// 错误信息
|
||||
private final String errorMsg;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.hanserwei.hannote.kv.biz.exception;
|
||||
|
||||
import com.hanserwei.framework.common.exception.ApiException;
|
||||
import com.hanserwei.framework.common.response.Response;
|
||||
import com.hanserwei.hannote.kv.biz.enums.ResponseCodeEnum;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings("LoggingSimilarMessage")
|
||||
@ControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 捕获自定义业务异常
|
||||
*
|
||||
* @return Response.fail(e)
|
||||
*/
|
||||
@ExceptionHandler({ApiException.class})
|
||||
@ResponseBody
|
||||
public Response<Object> handleApiException(HttpServletRequest request, ApiException e) {
|
||||
log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMsg());
|
||||
return Response.fail(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获参数校验异常
|
||||
*
|
||||
* @return Response.fail(errorCode, errorMessage)
|
||||
*/
|
||||
@ExceptionHandler({MethodArgumentNotValidException.class})
|
||||
@ResponseBody
|
||||
public Response<Object> handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
|
||||
// 参数错误异常码
|
||||
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||
|
||||
// 获取 BindingResult
|
||||
BindingResult bindingResult = e.getBindingResult();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com';
|
||||
Optional.of(bindingResult.getFieldErrors()).ifPresent(errors -> {
|
||||
errors.forEach(error ->
|
||||
sb.append(error.getField())
|
||||
.append(" ")
|
||||
.append(error.getDefaultMessage())
|
||||
.append(", 当前值: '")
|
||||
.append(error.getRejectedValue())
|
||||
.append("'; ")
|
||||
|
||||
);
|
||||
});
|
||||
|
||||
// 错误信息
|
||||
String errorMessage = sb.toString();
|
||||
|
||||
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||
|
||||
return Response.fail(errorCode, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 捕获 guava 参数校验异常
|
||||
*
|
||||
* @return Response.fail(ResponseCodeEnum.PARAM_NOT_VALID)
|
||||
*/
|
||||
@ExceptionHandler({IllegalArgumentException.class})
|
||||
@ResponseBody
|
||||
public Response<Object> handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
|
||||
// 参数错误异常码
|
||||
String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();
|
||||
|
||||
// 错误信息
|
||||
String errorMessage = e.getMessage();
|
||||
|
||||
log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);
|
||||
|
||||
return Response.fail(errorCode, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 其他类型异常
|
||||
*
|
||||
* @param request 请求
|
||||
* @param e 异常
|
||||
* @return Response.fail(ResponseCodeEnum.SYSTEM_ERROR)
|
||||
*/
|
||||
@ExceptionHandler({Exception.class})
|
||||
@ResponseBody
|
||||
public Response<Object> handleOtherException(HttpServletRequest request, Exception e) {
|
||||
log.error("{} request error, ", request.getRequestURI(), e);
|
||||
return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.hanserwei.hannote.kv.biz.service;
|
||||
|
||||
import com.hanserwei.framework.common.response.Response;
|
||||
import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO;
|
||||
import com.hanserwei.hannote.kv.dto.req.DeleteNoteContentReqDTO;
|
||||
import com.hanserwei.hannote.kv.dto.req.FindNoteContentReqDTO;
|
||||
import com.hanserwei.hannote.kv.dto.resp.FindNoteContentRspDTO;
|
||||
|
||||
public interface NoteContentService {
|
||||
/**
|
||||
* 添加笔记内容
|
||||
*
|
||||
* @param addNoteContentReqDTO 添加笔记内容请求参数
|
||||
* @return 响应
|
||||
*/
|
||||
Response<?> addNoteContent(AddNoteContentReqDTO addNoteContentReqDTO);
|
||||
|
||||
/**
|
||||
* 查询笔记内容
|
||||
*
|
||||
* @param findNoteContentReqDTO 查询笔记内容请求参数
|
||||
* @return 响应
|
||||
*/
|
||||
Response<FindNoteContentRspDTO> findNoteContent(FindNoteContentReqDTO findNoteContentReqDTO);
|
||||
|
||||
/**
|
||||
* 删除笔记内容
|
||||
*
|
||||
* @param deleteNoteContentReqDTO 删除笔记内容请求参数
|
||||
* @return 响应
|
||||
*/
|
||||
Response<?> deleteNoteContent(DeleteNoteContentReqDTO deleteNoteContentReqDTO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.hanserwei.hannote.kv.biz.service.impl;
|
||||
|
||||
import com.hanserwei.framework.common.exception.ApiException;
|
||||
import com.hanserwei.framework.common.response.Response;
|
||||
import com.hanserwei.hannote.kv.biz.domain.dataobject.NoteContentDO;
|
||||
import com.hanserwei.hannote.kv.biz.domain.repository.NoteContentRepository;
|
||||
import com.hanserwei.hannote.kv.biz.enums.ResponseCodeEnum;
|
||||
import com.hanserwei.hannote.kv.biz.service.NoteContentService;
|
||||
import com.hanserwei.hannote.kv.dto.req.AddNoteContentReqDTO;
|
||||
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 lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
public class NoteContentServiceImpl implements NoteContentService {
|
||||
|
||||
@Resource
|
||||
private NoteContentRepository noteContentRepository;
|
||||
|
||||
@Override
|
||||
public Response<?> addNoteContent(AddNoteContentReqDTO addNoteContentReqDTO) {
|
||||
// 笔记ID
|
||||
Long noteId = addNoteContentReqDTO.getNoteId();
|
||||
// 笔记内容
|
||||
String content = addNoteContentReqDTO.getContent();
|
||||
|
||||
NoteContentDO noteContent = NoteContentDO.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.content(content)
|
||||
.build();
|
||||
|
||||
// 插入数据
|
||||
noteContentRepository.save(noteContent);
|
||||
return Response.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<FindNoteContentRspDTO> findNoteContent(FindNoteContentReqDTO findNoteContentReqDTO) {
|
||||
// 笔记ID
|
||||
String noteId = findNoteContentReqDTO.getNoteId();
|
||||
Optional<NoteContentDO> optional = noteContentRepository.findById(UUID.fromString(noteId));
|
||||
if (optional.isEmpty()){
|
||||
throw new ApiException(ResponseCodeEnum.NOTE_CONTENT_NOT_FOUND);
|
||||
}
|
||||
NoteContentDO noteContentDO = optional.get();
|
||||
// 构建回参
|
||||
FindNoteContentRspDTO findNoteContentRspDTO = FindNoteContentRspDTO.builder()
|
||||
.noteId(noteContentDO.getId())
|
||||
.content(noteContentDO.getContent())
|
||||
.build();
|
||||
return Response.success(findNoteContentRspDTO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response<?> deleteNoteContent(DeleteNoteContentReqDTO deleteNoteContentReqDTO) {
|
||||
String noteId = deleteNoteContentReqDTO.getNoteId();
|
||||
noteContentRepository.deleteById(UUID.fromString(noteId));
|
||||
return Response.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
server:
|
||||
port: 8084 # 项目启动的端口
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: dev # 默认激活 dev 本地开发环境
|
||||
12
han-note-kv/han-note-kv-biz/src/main/resources/bootstrap.yml
Normal file
12
han-note-kv/han-note-kv-biz/src/main/resources/bootstrap.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
spring:
|
||||
application:
|
||||
name: han-note-kv # 应用名称
|
||||
profiles:
|
||||
active: dev # 默认激活 dev 本地开发环境
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
enabled: true # 启用服务发现
|
||||
group: DEFAULT_GROUP # 所属组
|
||||
namespace: han-note # 命名空间
|
||||
server-addr: 127.0.0.1:8848 # 指定 NaCos 配置中心的服务器地址
|
||||
@@ -0,0 +1,58 @@
|
||||
<configuration>
|
||||
<!-- 引用 Spring Boot 的 logback 基础配置 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
|
||||
|
||||
<!-- 应用名称 -->
|
||||
<property scope="context" name="appName" value="kv"/>
|
||||
<!-- 自定义日志输出路径,以及日志名称前缀 -->
|
||||
<property name="LOG_FILE" value="./logs/${appName}.%d{yyyy-MM-dd}"/>
|
||||
<!-- 每行日志输出的格式 -->
|
||||
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||
|
||||
<!-- 文件输出 -->
|
||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<!-- 日志文件的命名格式 -->
|
||||
<fileNamePattern>${LOG_FILE}-%i.log</fileNamePattern>
|
||||
<!-- 保留 30 天的日志文件 -->
|
||||
<maxHistory>30</maxHistory>
|
||||
<!-- 单个日志文件最大大小 -->
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
<!-- 日志文件的总大小,0 表示不限制 -->
|
||||
<totalSizeCap>0</totalSizeCap>
|
||||
<!-- 重启服务时,是否清除历史日志,不推荐清理 -->
|
||||
<cleanHistoryOnStart>false</cleanHistoryOnStart>
|
||||
</rollingPolicy>
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<pattern>${LOG_PATTERN}</pattern>
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 异步写入日志,提升性能 -->
|
||||
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<!-- 是否丢弃日志, 0 表示不丢弃。默认情况下,如果队列满 80%, 会丢弃 TRACE、DEBUG、INFO 级别的日志 -->
|
||||
<discardingThreshold>0</discardingThreshold>
|
||||
<!-- 队列大小。默认值为 256 -->
|
||||
<queueSize>256</queueSize>
|
||||
<appender-ref ref="FILE"/>
|
||||
</appender>
|
||||
|
||||
<!-- 本地 dev 开发环境 -->
|
||||
<springProfile name="dev">
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/> <!-- 输出控制台日志 -->
|
||||
<appender-ref ref="ASYNC_FILE"/> <!-- 打印日志到文件中。PS: 本地环境下,如果不想打印日志到文件,可注释掉此行 -->
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
<!-- 其它环境 -->
|
||||
<springProfile name="prod">
|
||||
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
|
||||
<root level="INFO">
|
||||
<appender-ref ref="ASYNC_FILE"/> <!-- 生产环境下,仅打印日志到文件中 -->
|
||||
</root>
|
||||
</springProfile>
|
||||
|
||||
</configuration>
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.hanserwei.hannote.kv.biz;
|
||||
|
||||
import com.hanserwei.framework.common.utils.JsonUtils;
|
||||
import com.hanserwei.hannote.kv.biz.domain.dataobject.NoteContentDO;
|
||||
import com.hanserwei.hannote.kv.biz.domain.repository.NoteContentRepository;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@SpringBootTest
|
||||
@Slf4j
|
||||
class CassandraTests {
|
||||
|
||||
@Resource
|
||||
private NoteContentRepository noteContentRepository;
|
||||
|
||||
/**
|
||||
* 测试插入数据
|
||||
*/
|
||||
@Test
|
||||
void testInsert() {
|
||||
NoteContentDO nodeContent = NoteContentDO.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.content("代码测试笔记内容插入")
|
||||
.build();
|
||||
|
||||
noteContentRepository.save(nodeContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试修改数据
|
||||
*/
|
||||
@Test
|
||||
void testUpdate() {
|
||||
NoteContentDO nodeContent = NoteContentDO.builder()
|
||||
.id(UUID.fromString("8a0e491d-49f0-41cf-95b1-c9f541567156"))
|
||||
.content("代码测试笔记内容更新")
|
||||
.build();
|
||||
|
||||
noteContentRepository.save(nodeContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试查询数据
|
||||
*/
|
||||
@Test
|
||||
void testSelect() {
|
||||
Optional<NoteContentDO> optional = noteContentRepository.findById(UUID.fromString("8a0e491d-49f0-41cf-95b1-c9f541567156"));
|
||||
optional.ifPresent(noteContentDO -> log.info("查询结果:{}", JsonUtils.toJsonString(noteContentDO)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试删除数据
|
||||
*/
|
||||
@Test
|
||||
void testDelete() {
|
||||
noteContentRepository.deleteById(UUID.fromString("8a0e491d-49f0-41cf-95b1-c9f541567156"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user