refactor(utils): 重构工具类并添加新功能

- 重命名ApiResponseUtil为ApiResponseUtils,MarkdownAST保持不变,PaginationUtil为PaginationUtils,SecurityUtil为SecurityUtils- 添加MarkdownUtils类,分离Markdown相关功能
- 新增SearchUtils类,用于搜索相关功能
- 添加TraceIdFilter过滤器,用于追踪ID
- 新增NeedLoginAspect切面,用于登录验证- 更新相关类的引用和使用
This commit is contained in:
LingandRX 2025-04-25 01:01:53 +08:00
parent 439e7e1633
commit ae5b7cbd1a
17 changed files with 209 additions and 54 deletions

View File

@ -28,6 +28,8 @@ repositories {
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
@ -49,8 +51,12 @@ dependencies {
testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.4'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'com.huaban:jieba-analysis:1.0.2'
implementation 'org.aspectj:aspectjweaver:1.9.19'
implementation 'org.springframework:spring-aop:6.1.0'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
}
tasks.named('test') {
useJUnitPlatform()
}
}

View File

@ -2,8 +2,10 @@ package com.example.copykamanotes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = "com.example.copykamanotes")
@EnableScheduling
public class NotesApplication {
public static void main(String[] args) {

View File

@ -0,0 +1,31 @@
package com.example.copykamanotes.aspect;
import com.example.copykamanotes.annotation.NeedLogin;
import com.example.copykamanotes.scope.RequestScopeData;
import com.example.copykamanotes.utils.ApiResponseUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class NeedLoginAspect {
@Autowired
private RequestScopeData requestScopeData;
@Around("@annotation(needLogin)")
public Object around(ProceedingJoinPoint joinPoint, NeedLogin needLogin) throws Throwable {
if (!requestScopeData.isLogin()) {
return ApiResponseUtils.error("请先登录");
}
if (requestScopeData.getUserId() == null ) {
return ApiResponseUtils.error("用户不存在");
}
return joinPoint.proceed();
}
}

View File

@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@ -23,7 +24,7 @@ public class SecurityConfig {
.requestMatchers("/api/**").permitAll() // 允许 /api/** 路径下的所有请求
.anyRequest().authenticated() // 其他请求需要认证
)
.csrf(csrf -> csrf.disable()); // 根据需要禁用 CSRF
.csrf(AbstractHttpConfigurer::disable); // 根据需要禁用 CSRF
return http.build();
}

View File

@ -1,9 +1,14 @@
package com.example.copykamanotes.config;
import com.example.copykamanotes.filter.TraceIdFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.example.copykamanotes.interceptor.TokenInterceptor;
@ -13,6 +18,14 @@ public class WebConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Value("${upload.path:D:/kamaNotes/upload")
private String uploadPath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**")
.addResourceLocations("file:" + uploadPath + "/");
}
/**
* 添加拦截器, 拦截所有请求除了登录和注册
@ -35,4 +48,11 @@ public class WebConfig implements WebMvcConfigurer {
.maxAge(3600);
}
@Bean
public FilterRegistrationBean<TraceIdFilter> traceIdFilter() {
FilterRegistrationBean<TraceIdFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new TraceIdFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}

View File

@ -0,0 +1,34 @@
package com.example.copykamanotes.filter;
import org.slf4j.MDC;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.io.IOException;
public class TraceIdFilter implements Filter {
private static final String TRACE_ID_KEY = "trace_id";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request, response);
} finally {
MDC.remove(TRACE_ID_KEY);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}

View File

@ -16,9 +16,9 @@ import com.example.copykamanotes.service.NoteLikeService;
import com.example.copykamanotes.service.NoteService;
import com.example.copykamanotes.service.QuestionService;
import com.example.copykamanotes.service.UserService;
import com.example.copykamanotes.utils.ApiResponseUtil;
import com.example.copykamanotes.utils.MarkdownUtil;
import com.example.copykamanotes.utils.PaginationUtil;
import com.example.copykamanotes.utils.ApiResponseUtils;
import com.example.copykamanotes.utils.MarkdownUtils;
import com.example.copykamanotes.utils.PaginationUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -48,7 +48,7 @@ public class NoteServiceImpl implements NoteService {
@Override
public ApiResponse<List<NoteVO>> getNotes(NoteQueryParams noteQueryParams) {
int offset = PaginationUtil.calculateOffset(noteQueryParams.getPage(), noteQueryParams.getPageSize());
int offset = PaginationUtils.calculateOffset(noteQueryParams.getPage(), noteQueryParams.getPageSize());
int total = noteMapper.countNotes(noteQueryParams);
@ -105,9 +105,9 @@ public class NoteServiceImpl implements NoteService {
userActionsVO.setIsCollected(true);
}
if (MarkdownUtil.needCollapsed(note.getContent())) {
if (MarkdownUtils.needCollapsed(note.getContent())) {
noteVO.setNeedCollapsed(true);
noteVO.setDisplayContent(MarkdownUtil.extractIntroduction(note.getContent()));
noteVO.setDisplayContent(MarkdownUtils.extractIntroduction(note.getContent()));
} else {
noteVO.setNeedCollapsed(false);
}
@ -116,9 +116,9 @@ public class NoteServiceImpl implements NoteService {
return noteVO;
}).toList();
return ApiResponseUtil.success("获取笔记列表成功", noteVOs, pagination);
return ApiResponseUtils.success("获取笔记列表成功", noteVOs, pagination);
} catch (Exception e) {
return ApiResponseUtil.error("获取笔记列表失败");
return ApiResponseUtils.error("获取笔记列表失败");
}
}

View File

@ -4,7 +4,7 @@ import com.example.copykamanotes.model.base.ApiResponse;
import com.example.copykamanotes.model.vo.upload.ImageVO;
import com.example.copykamanotes.service.FileService;
import com.example.copykamanotes.service.UploadService;
import com.example.copykamanotes.utils.ApiResponseUtil;
import com.example.copykamanotes.utils.ApiResponseUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@ -20,6 +20,6 @@ public class UploadServiceImpl implements UploadService {
String url = fileService.uploadImage(file);
ImageVO imageVO = new ImageVO();
imageVO.setUrl(url);
return ApiResponseUtil.success("上传成功", imageVO);
return ApiResponseUtils.success("上传成功", imageVO);
}
}

View File

@ -17,10 +17,9 @@ import com.example.copykamanotes.scope.RequestScopeData;
import com.example.copykamanotes.service.EmailService;
import com.example.copykamanotes.service.FileService;
import com.example.copykamanotes.service.UserService;
import com.example.copykamanotes.utils.ApiResponseUtil;
import com.example.copykamanotes.utils.ApiResponseUtils;
import com.example.copykamanotes.utils.JwtUtil;
import com.example.copykamanotes.utils.PaginationUtil;
import com.google.protobuf.Api;
import com.example.copykamanotes.utils.PaginationUtils;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@ -62,14 +61,14 @@ public class UserServiceImpl implements UserService {
public ApiResponse<RegisterVO> register(RegisterRequest registerRequest) {
User existingUser = userMapper.findByAccount(registerRequest.getAccount());
if (existingUser != null) {
return ApiResponseUtil.error("账号重复");
return ApiResponseUtils.error("账号重复");
}
if (registerRequest.getEmail() != null && !registerRequest.getEmail().isEmpty()) {
// 验证邮箱是否重复
existingUser = userMapper.findByEmail(registerRequest.getEmail());
if (existingUser != null) {
return ApiResponseUtil.error("邮箱重复");
return ApiResponseUtils.error("邮箱重复");
}
// if (registerRequest.getVerifyCode() == null || registerRequest.getVerifyCode().isEmpty()) {
@ -95,10 +94,10 @@ public class UserServiceImpl implements UserService {
BeanUtils.copyProperties(user, registerVO);
userMapper.updateLastLoginAt(user.getUserId());
return ApiResponseUtil.success("注册成功", registerVO, token);
return ApiResponseUtils.success("注册成功", registerVO, token);
} catch (Exception e) {
log.error("注册失败", e);
return ApiResponseUtil.error("注册失败,请稍后再试");
return ApiResponseUtils.error("注册失败,请稍后再试");
}
}
@ -111,15 +110,15 @@ public class UserServiceImpl implements UserService {
} else if (loginRequest.getEmail() != null && !loginRequest.getEmail().isEmpty()) {
user = userMapper.findByEmail(loginRequest.getEmail());
} else {
return ApiResponseUtil.error("账号或邮箱不能为空");
return ApiResponseUtils.error("账号或邮箱不能为空");
}
if (user == null) {
return ApiResponseUtil.error("账号或邮箱不存在");
return ApiResponseUtils.error("账号或邮箱不存在");
}
if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
return ApiResponseUtil.error("密码错误");
return ApiResponseUtils.error("密码错误");
}
String token = jwtUtil.generateToken(user.getUserId());
@ -129,7 +128,7 @@ public class UserServiceImpl implements UserService {
userMapper.updateLastLoginAt(user.getUserId());
return ApiResponseUtil.success("登录成功", loginUserVO, token);
return ApiResponseUtils.success("登录成功", loginUserVO, token);
}
@Override
@ -137,26 +136,26 @@ public class UserServiceImpl implements UserService {
Long userId = requestScopeData.getUserId();
if (userId == null) {
return ApiResponseUtil.error("未登录");
return ApiResponseUtils.error("未登录");
}
try {
User user = userMapper.findById(userId);
if (user == null) {
return ApiResponseUtil.error("用户不存在");
return ApiResponseUtils.error("用户不存在");
}
String newToken = jwtUtil.generateToken(userId);
if (newToken == null) {
return ApiResponseUtil.error("token生成失败");
return ApiResponseUtils.error("token生成失败");
}
LoginUserVO loginUserVO = new LoginUserVO();
BeanUtils.copyProperties(user, loginUserVO);
return ApiResponseUtil.success("获取用户信息成功", loginUserVO, newToken);
return ApiResponseUtils.success("获取用户信息成功", loginUserVO, newToken);
} catch (Exception e) {
log.error("获取用户信息失败", e);
return ApiResponseUtil.error("获取用户信息失败");
return ApiResponseUtils.error("获取用户信息失败");
}
}
@ -165,12 +164,12 @@ public class UserServiceImpl implements UserService {
User user = userMapper.findById(userId);
if (user == null) {
return ApiResponseUtil.error("用户不存在");
return ApiResponseUtils.error("用户不存在");
}
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return ApiResponseUtil.success("获取用户信息成功", userVO);
return ApiResponseUtils.success("获取用户信息成功", userVO);
}
@Override
@ -180,7 +179,7 @@ public class UserServiceImpl implements UserService {
Long userId = requestScopeData.getUserId();
if (userId == null) {
return ApiResponseUtil.error("未登录");
return ApiResponseUtils.error("未登录");
}
User user = new User();
@ -189,10 +188,10 @@ public class UserServiceImpl implements UserService {
try {
userMapper.update(user);
return ApiResponseUtil.success("更新用户信息成功");
return ApiResponseUtils.success("更新用户信息成功");
} catch (Exception e) {
log.error("更新用户信息失败", e);
return ApiResponseUtil.error("更新用户信息失败");
return ApiResponseUtils.error("更新用户信息失败");
}
}
@ -210,15 +209,15 @@ public class UserServiceImpl implements UserService {
// 分页数据
int total = userMapper.countByQueryParam(userQueryParam);
int offset = PaginationUtil.calculateOffset(userQueryParam.getPage(), userQueryParam.getPageSize());
int offset = PaginationUtils.calculateOffset(userQueryParam.getPage(), userQueryParam.getPageSize());
Pagination pagination = new Pagination(userQueryParam.getPage(), userQueryParam.getPageSize(), total);
try {
List<User> users = userMapper.findByQueryParam(userQueryParam, userQueryParam.getPageSize(), offset);
return ApiResponseUtil.success("获取用户列表成功", users, pagination);
return ApiResponseUtils.success("获取用户列表成功", users, pagination);
} catch (Exception e) {
return ApiResponseUtil.error(e.getMessage());
return ApiResponseUtils.error(e.getMessage());
}
}
@ -228,10 +227,10 @@ public class UserServiceImpl implements UserService {
String url = fileService.uploadImage(file);
AvatarVO avatarVO = new AvatarVO();
avatarVO.setAvatarUrl(url);
return ApiResponseUtil.success("上传成功", avatarVO);
return ApiResponseUtils.success("上传成功", avatarVO);
} catch (Exception e) {
log.error("上传失败", e);
return ApiResponseUtil.error("上传失败");
return ApiResponseUtils.error("上传失败");
}
}
@ -240,27 +239,27 @@ public class UserServiceImpl implements UserService {
Long userId = requestScopeData.getUserId();
if (userId == null) {
return ApiResponseUtil.error("未登录");
return ApiResponseUtils.error("未登录");
}
User user = null;
user = userMapper.findById(userId);
if (user == null || !passwordEncoder.matches(oldPassword, user.getPassword())) {
return ApiResponseUtil.error("旧密码错误");
return ApiResponseUtils.error("旧密码错误");
}
if (Objects.equals(oldPassword, newPassword)) {
return ApiResponseUtil.error("新密码不能与旧密码相同");
return ApiResponseUtils.error("新密码不能与旧密码相同");
}
user.setPassword(passwordEncoder.encode(newPassword));
try {
userMapper.updatePassword(user.getUserId(), user.getPassword());
return ApiResponseUtil.success("更新密码成功");
return ApiResponseUtils.success("更新密码成功");
} catch (Exception e) {
log.error("更新密码失败", e);
return ApiResponseUtil.error("更新密码失败");
return ApiResponseUtils.error("更新密码失败");
}
}
}

View File

@ -7,7 +7,7 @@ import com.example.copykamanotes.model.base.PaginationApiResponse;
import com.example.copykamanotes.model.base.TokenApiResponse;
import org.springframework.http.HttpStatus;
public class ApiResponseUtil {
public class ApiResponseUtils {
/**
* 构建成功的响应

View File

@ -44,8 +44,9 @@ public class JwtUtil {
*/
public Long getUserIdFromToken(String token) {
try {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
Claims claims = Jwts
.parserBuilder()
.setSigningKey(SECRET_KEY).build()
.parseClaimsJws(token)
.getBody();
return Long.valueOf(claims.get("userId").toString());
@ -60,7 +61,8 @@ public class JwtUtil {
*/
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token);
// Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (Exception e) {
return false;

View File

@ -15,20 +15,24 @@ public class MarkdownAST {
private final Document markdownAST;
private final String markdownText;
public MarkdownAST( String markdownText) {
// 构造函数初始化并解析 Markdown文本
public MarkdownAST(String markdownText) {
this.markdownText = markdownText;
Parser parser = Parser.builder().build();
// 解析 Markdown 文本
this.markdownAST = parser.parse(markdownText);
}
public String extractIntroduction(int maxChars) {
StringBuilder introText = new StringBuilder();
// 遍历 AST 节点提取文本内容
for (Node node : markdownAST.getChildren()) {
if (node instanceof Heading || node instanceof Paragraph) {
// 获取节点的文本内容
String renderedText = getNoteText(node);
// 计算剩余字符数并截取文本
int remainingChars = maxChars - introText.length();
introText.append(renderedText, 0, Math.min(remainingChars, renderedText.length()));
@ -41,26 +45,30 @@ public class MarkdownAST {
return introText.toString().trim() + "...";
}
// 检查Markdown文本中是否包含图片
public List<String> extractImages() {
List<String> images = new ArrayList<>();
List<String> imageUrls = new ArrayList<>();
for (Node node : markdownAST.getChildren()) {
if (node instanceof Image imageNode) {
images.add(((Image) node).getUrl().toString());
imageUrls.add(imageNode.getUrl().toString());
}
}
return images;
return imageUrls;
}
// 检查Markdown文本是否需要折叠
public boolean shouldCollapse(int maxChars) {
return hasImages() || markdownText.length() > maxChars;
}
// 获取折叠后的Markdown文本
public String getCollapseMarkdown() {
String introText = extractIntroduction(150);
return introText + "...";
}
// 获取节点的文本内容
private String getNoteText(Node node) {
StringBuilder text = new StringBuilder();
@ -74,6 +82,7 @@ public class MarkdownAST {
return text.toString();
}
// 检查Markdown文本中是否包含图片
private boolean hasImages() {
return !extractImages().isEmpty();
}

View File

@ -1,6 +1,7 @@
package com.example.copykamanotes.utils;
public class MarkdownUtil {
public static boolean needCollapsed(String markdown) {
MarkdownAST ast = new MarkdownAST(markdown);
return ast.shouldCollapse(250);

View File

@ -0,0 +1,13 @@
package com.example.copykamanotes.utils;
public class MarkdownUtils {
public static boolean needCollapsed(String markdown) {
MarkdownAST ast = new MarkdownAST(markdown);
return ast.shouldCollapse(250);
}
public static String extractIntroduction(String markdown) {
MarkdownAST ast = new MarkdownAST(markdown);
return ast.extractIntroduction(250);
}
}

View File

@ -1,6 +1,6 @@
package com.example.copykamanotes.utils;
public class PaginationUtil {
public class PaginationUtils {
/**
* 计算分页参数
*

View File

@ -0,0 +1,37 @@
package com.example.copykamanotes.utils;
import com.huaban.analysis.jieba.JiebaSegmenter;
import org.springframework.util.StringUtils;
import java.util.List;
public class SearchUtils {
private static final JiebaSegmenter segmenter = new JiebaSegmenter();
/**
* 预处理搜索关键词
* @param keyword
* @return
*/
public static String preprocessKeyword(String keyword) {
if (!StringUtils.hasText(keyword)) {
return "";
}
keyword = keyword.replaceAll("[\\p{P}\\p{S}]", " ");
List<String> words = segmenter.sentenceProcess(keyword);
return String.join(" ", words);
}
/**
* 计算分页的偏移量
* @param page
* @param pageSize
* @return
*/
public static int calculateOffset(int page, int pageSize) {
return Math.max(0, (page - 1) * pageSize);
}
}

View File

@ -4,7 +4,7 @@ import com.example.copykamanotes.model.entity.User;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityUtil {
public class SecurityUtils {
/**
* 获取当前登录用户的ID
*