package com.vincent.rsf.server.ai.store; import com.vincent.rsf.server.ai.config.AiDefaults; import com.vincent.rsf.server.ai.store.support.AiRedisExecutor; import com.vincent.rsf.server.ai.store.support.AiRedisKeys; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import java.time.Instant; import java.util.UUID; @Component @RequiredArgsConstructor public class AiChatRateLimiter { private final AiRedisExecutor aiRedisExecutor; private final AiRedisKeys aiRedisKeys; public boolean allowChatRequest(Long tenantId, Long userId, String promptCode) { String key = aiRedisKeys.buildRateLimitKey(tenantId, userId, promptCode); long now = Instant.now().toEpochMilli(); long windowStart = now - (AiDefaults.CHAT_RATE_LIMIT_WINDOW_SECONDS * 1000L); Boolean allowed = aiRedisExecutor.execute(jedis -> { jedis.zremrangeByScore(key, 0, windowStart); long count = jedis.zcard(key); if (count >= AiDefaults.CHAT_RATE_LIMIT_MAX_REQUESTS) { jedis.expire(key, AiDefaults.CHAT_RATE_LIMIT_WINDOW_SECONDS); return Boolean.FALSE; } jedis.zadd(key, now, now + ":" + UUID.randomUUID()); jedis.expire(key, AiDefaults.CHAT_RATE_LIMIT_WINDOW_SECONDS); return Boolean.TRUE; }); return Boolean.TRUE.equals(allowed); } }