将显示给应用程序用户的错误信息安全地存储到数据库中

后端开发 2026-07-08

最终,我希望减少重复出现的相同错误信息,甚至防止潜在的DDoS攻击。我已经在Spring Boot应用中实现了以下方法。目标是在同一个会话中,持续重复同一操作的用户能够收到相同的消息,并且拥有相同的UUID。

@Slf4j
@Service
@RequiredArgsConstructor
public class FrontendEventsService {

    private final ErrorMessageRepository messageRepository;
    private final JwtService jwtService;
    private final DateTimeProvider dateTimeProvider;

    @Value("${error.message.limit}")
    private String messagesLimit;

    /**
     * Registering the error message presented to the User.
     *
     * @param message error message.
     */
    public ErrorResponseDto handleMessage(ErrorResponseDto message) {
        String jwtToken = StringJwtParser.getTokenShortcut(jwtService.getRawToken());
        int limit = messagesLimit != null ? Integer.parseInt(messagesLimit) : 10;
        if (messageRepository.lastMinuteMessageCounter(jwtToken, dateTimeProvider.getCurrentDateTime()) >= limit) {
            log.warn("Error write limiter [{}] [{}]", jwtToken, message.getMessage());
            return null;
        }

        ErrorMessageEntity entity = ErrorMessageEntity.builder()
                        .createdAt(dateTimeProvider.getCurrentDateTime())
                        .uuid(getRandomUUID())
                        .tokenHash(jwtToken)
                        .userLogin(jwtService.getUserLogin())
                        .message(message.getMessage())
                        .url(message.getUrl())
                        .build();
        ErrorMessageEntity saved =  messageRepository.save(entity);
        return ErrorMessageMapper.toDto(saved);
    }

但我担心,单纯通过数据库查询逐条统计消息,而不考虑内容本身,这并不是一个好办法。有没有人开发过在单位时间内限制同一内容被写入的次数的方法,并且避免不必要的数据库提交?

另一方面,若每个用户在缓存中的条目数量被限定,是否会在生产环境中消耗过多的应用资源?

解决方案

然而,我决定通过检查刚刚捕获的错误的hashCode,以及给定用户在最近一分钟内保存的错误,来限制新UUID的保存与返回,而不是在不核验任何错误参数的情况下,仅通过按单位时间限制保存次数来实现。

        if (userPrincipal instanceof AbstractAuthenticationToken && ((AbstractAuthenticationToken) userPrincipal).isAuthenticated()) {
            Timestamp endDate = appDateTimeProvider.getCurrentTimestamp();
            Timestamp startDate = new Timestamp(endDate.getTime() - (60 * 1000)); // one minute before
            List<ErrorMessageEntity> userErrors = errorMessageService.getByUserLoginAndTimeRange(userLogin, startDate, endDate);
            final ErrorResponseDto finalErrorResponseDto = errorResponseDto;
            List<ErrorMessageEntity> matched = userErrors.stream()
                    .filter(Objects::nonNull)
                    .filter(error -> error.hashCode() == finalErrorResponseDto.hashCode())
                    .collect(Collectors.toList());
            if (matched.isEmpty()) {
                // async process do not stop creation
                errorMessageService.insertErrorMessageAsync(errorResponseDto);
            } else {
                log.debug("Found matched errors qty: {}", matched.size());
                errorResponseDto = ErrorMessageMapper.toDto(matched.get(matched.size() - 1));
            }
        }

为此,在ErrorMessageEntity实体和ErrorResponseDto dto中,我覆盖了从 Object 继承而来的 hashCode() 方法。

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + (errorCode!= null ? errorCode.hashCode() : 0);
        result = 31 * result + (message != null ? message.hashCode() : 0);
        result = 31 * result + (url != null ? url.hashCode() : 0);
        result = 31 * result + (tokenHash != null ? tokenHash.hashCode() : 0);
        return result;
    }

这使得可以快速将最近记录的错误列表与当前正在处理的错误进行比对。如果数据库中存储的任何错误具有相同的 hashCode,则在不保存新捕获的错误的情况下,将其发送给前端。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章