Merge remote-tracking branch 'origin/master'

master
wyt 4 months ago
commit ca36b136d1

@ -1,7 +1,6 @@
package com.aurora.interceptor;
import com.alibaba.fastjson.JSON;
import com.aurora.annotation.AccessLimit;
import com.aurora.model.vo.ResultVO;
import com.aurora.service.RedisService;
@ -20,47 +19,106 @@ import java.nio.charset.StandardCharsets;
import static com.aurora.constant.CommonConstant.APPLICATION_JSON;
/**
* 访 HandlerInterceptor
* 访
*/
@Log4j2
@Component
@SuppressWarnings("all")
public class AccessLimitInterceptor implements HandlerInterceptor {
// 自动注入 RedisService用于与 Redis 进行交互,实现访问次数的计数和过期时间设置
@Autowired
private RedisService redisService;
/**
*
* Controller
*
* @param httpServletRequest HTTP
* @param httpServletResponse HTTP
* @param handler HandlerMethod Controller
* @return true false
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
// 检查当前的 handler 是否是 HandlerMethod 类型,即是否对应一个 Controller 方法
if (handler instanceof HandlerMethod) {
// 将 handler 强制转换为 HandlerMethod 类型,以便获取方法级别的注解
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取当前方法上的 @AccessLimit 注解,用于获取访问限制的配置(如时间窗口、最大访问次数)
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
// 如果当前方法没有被 @AccessLimit 注解修饰,则不进行访问限制,直接放行
if (accessLimit != null) {
// 从 @AccessLimit 注解中获取时间窗口(秒)和最大访问次数
long seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
// 生成用于 Redis 计数的唯一键,通常结合客户端 IP 和方法名,确保不同用户和方法有不同的计数
String key = IpUtil.getIpAddress(httpServletRequest) + "-" + handlerMethod.getMethod().getName();
try {
// 使用 RedisService 的 incrExpire 方法,对指定键进行自增操作,并设置过期时间为指定的时间窗口
// incrExpire 方法返回当前键的计数值
long q = redisService.incrExpire(key, seconds);
// 如果当前计数值超过了最大访问次数,表示请求过于频繁,触发访问限制
if (q > maxCount) {
// 调用 render 方法,向客户端返回一个表示访问限制的 JSON 响应
render(httpServletResponse, ResultVO.fail("请求过于频繁," + seconds + "秒后再试"));
// 记录警告日志,提示请求次数超过限制
log.warn(key + "请求次数超过每" + seconds + "秒" + maxCount + "次");
// 返回 false中断请求处理链不再继续处理该请求
return false;
}
// 如果当前计数值未超过最大访问次数,允许请求继续处理
return true;
} catch (RedisConnectionFailureException e) {
// 如果在访问 Redis 时发生连接失败异常,记录警告日志
log.warn("redis错误: " + e.getMessage());
// 返回 false中断请求处理链不再继续处理该请求
return false;
}
}
}
// 如果当前请求没有 @AccessLimit 注解,或者不是 HandlerMethod 类型,直接放行
return true;
}
/**
* JSON
* ResultVO JSON HTTP
*
* @param response HTTP
* @param resultVO ResultVO
* @throws Exception
*/
private void render(HttpServletResponse response, ResultVO<?> resultVO) throws Exception {
// 设置响应的内容类型为 JSON确保客户端正确解析响应体
response.setContentType(APPLICATION_JSON);
// 获取响应的输出流,用于写入响应数据
OutputStream out = response.getOutputStream();
// 将 ResultVO 对象序列化为 JSON 字符串
String str = JSON.toJSONString(resultVO);
// 将 JSON 字符串写入响应输出流
out.write(str.getBytes(StandardCharsets.UTF_8));
// 刷新输出流,确保数据被发送到客户端
out.flush();
// 关闭输出流,释放资源
out.close();
}
}
}

@ -13,23 +13,57 @@ import java.util.Optional;
import static com.aurora.constant.CommonConstant.*;
/**
* HandlerInterceptor
* HTTP
* PageUtil 便便使
* 线
*/
@Component
@SuppressWarnings("all")
public class PaginationInterceptor implements HandlerInterceptor {
/**
*
* Controller
*
* @param request HTTP
* @param response HTTP
* @param handler HandlerMethod Controller
* @return true false
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 从 HTTP 请求中获取名为 "current" 的参数,表示当前页码
String currentPage = request.getParameter(CURRENT);
// 从 HTTP 请求中获取名为 "size" 的参数,表示每页大小(页容量)
// 如果 "size" 参数不存在或为 null则使用默认的每页大小 DEFAULT_SIZE
String pageSize = Optional.ofNullable(request.getParameter(SIZE)).orElse(DEFAULT_SIZE);
// 检查当前页码参数是否非空且不为空字符串
if (!Objects.isNull(currentPage) && !StringUtils.isEmpty(currentPage)) {
// 将当前页码和每页大小转换为 Long 类型,并创建一个 MyBatis-Plus 的 Page 对象
// Page 对象包含当前页码和每页大小,用于分页查询
PageUtil.setCurrentPage(new Page<>(Long.parseLong(currentPage), Long.parseLong(pageSize)));
}
// 返回 true表示继续处理请求
return true;
}
/**
* 线
*
*
* @param request HTTP
* @param response HTTP
* @param handler
* @param ex
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 调用 PageUtil 的 remove 方法,清除当前线程的分页信息,确保下一个请求不会受到上一个请求分页信息的影响
PageUtil.remove();
}
}

@ -11,25 +11,49 @@ import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* OperationLogEventExceptionLogEvent
* Spring
*
*/
@Component
public class AuroraListener {
// 自动注入 OperationLogMapper用于将操作日志保存到数据库
@Autowired
private OperationLogMapper operationLogMapper;
// 自动注入 ExceptionLogMapper用于将异常日志保存到数据库
@Autowired
private ExceptionLogMapper exceptionLogMapper;
/**
* OperationLogEvent
* OperationLogEvent
*
*
* @param operationLogEvent
*/
@Async
@EventListener(OperationLogEvent.class)
public void saveOperationLog(OperationLogEvent operationLogEvent) {
// 从事件中获取源对象(即操作日志对象),并将其强制转换为 OperationLog 类型
// 然后调用 OperationLogMapper 的 insert 方法,将操作日志保存到数据库
operationLogMapper.insert((OperationLog) operationLogEvent.getSource());
}
/**
* ExceptionLogEvent
* ExceptionLogEvent
*
*
* @param exceptionLogEvent
*/
@Async
@EventListener(ExceptionLogEvent.class)
public void saveExceptionLog(ExceptionLogEvent exceptionLogEvent) {
// 从事件中获取源对象(即异常日志对象),并将其强制转换为 ExceptionLog 类型
// 然后调用 ExceptionLogMapper 的 insert 方法,将异常日志保存到数据库
exceptionLogMapper.insert((ExceptionLog) exceptionLogEvent.getSource());
}
}
}

@ -8,13 +8,34 @@ import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
/**
*
* Spring
*
*/
@Service
public class ArticleImportStrategyContext {
/**
* Map Markdown
* ArticleImportStrategy
* Spring ArticleImportStrategy Bean Map
* Bean
*/
@Autowired
private Map<String, ArticleImportStrategy> articleImportStrategyMap;
/**
*
*
* @param file Markdown
* @param type 使 Markdown
*/
public void importArticles(MultipartFile file, String type) {
// 根据传入的类型参数,获取对应的 Markdown 类型枚举值
// 然后通过该枚举值获取对应的策略标识符(通常是枚举的 name 或自定义的 key
// 最后从 articleImportStrategyMap 中获取对应的 ArticleImportStrategy 实例
// 并调用其 importArticles 方法,传入上传的文件,执行具体的导入逻辑
articleImportStrategyMap.get(MarkdownTypeEnum.getMarkdownType(type)).importArticles(file);
}
}

@ -1,6 +1,5 @@
package com.aurora.strategy.context;
import com.aurora.model.dto.UserInfoDTO;
import com.aurora.enums.LoginTypeEnum;
import com.aurora.strategy.SocialLoginStrategy;
@ -9,14 +8,35 @@ import org.springframework.stereotype.Service;
import java.util.Map;
/**
*
* Spring
*
*/
@Service
public class SocialLoginStrategyContext {
/**
* Map
* SocialLoginStrategy
* Spring SocialLoginStrategy Bean Map
* Bean
*/
@Autowired
private Map<String, SocialLoginStrategy> socialLoginStrategyMap;
/**
*
*
*
* @param data
* @param loginTypeEnum 使QQGitHub
* @return UserInfoDTO
*/
public UserInfoDTO executeLoginStrategy(String data, LoginTypeEnum loginTypeEnum) {
// 根据传入的登录类型,获取对应的策略标识符(通常是枚举的策略键)
// 然后从 socialLoginStrategyMap 中获取对应的 SocialLoginStrategy 实例
// 并调用其 login 方法,传入登录数据,执行具体的登录逻辑,返回用户信息
return socialLoginStrategyMap.get(loginTypeEnum.getStrategy()).login(data);
}
}

@ -11,21 +11,58 @@ import java.util.Map;
import static com.aurora.enums.UploadModeEnum.getStrategy;
/**
*
* Spring
*
*/
@Service
public class UploadStrategyContext {
/**
* application.properties application.yml
* 使
*/
@Value("${upload.mode}")
private String uploadMode;
/**
* Map
* UploadStrategy
* Spring UploadStrategy Bean Map
* Bean
*/
@Autowired
private Map<String, UploadStrategy> uploadStrategyMap;
/**
*
* MultipartFile URL
*
* @param file MultipartFile
* @param path
* @return 访URL
*/
public String executeUploadStrategy(MultipartFile file, String path) {
// 根据配置的上传模式,获取对应的策略标识符(通常是枚举的 name 或自定义的 key
// 然后从 uploadStrategyMap 中获取对应的 UploadStrategy 实例
// 并调用其 uploadFile 方法,传入 MultipartFile 文件和路径,执行具体的上传逻辑,返回上传结果
return uploadStrategyMap.get(getStrategy(uploadMode)).uploadFile(file, path);
}
/**
*
* InputStream URL
*
* @param fileName
* @param inputStream
* @param path
* @return 访URL
*/
public String executeUploadStrategy(String fileName, InputStream inputStream, String path) {
// 根据配置的上传模式,获取对应的策略标识符(通常是枚举的 name 或自定义的 key
// 然后从 uploadStrategyMap 中获取对应的 UploadStrategy 实例
// 并调用其 uploadFile 方法传入文件名、InputStream 输入流和路径,执行具体的上传逻辑,返回上传结果
return uploadStrategyMap.get(getStrategy(uploadMode)).uploadFile(fileName, inputStream, path);
}
}

Loading…
Cancel
Save