Refactor Stage 1.1-1.3: Create repository layer, move util to core/security and integration/nekos

This commit is contained in:
loki5512344 2026-05-11 14:27:50 +02:00
parent 17f979b5d3
commit 703d0b7e49
28 changed files with 142 additions and 52 deletions

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.games;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.games;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.games;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.games;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.games;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.games;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.games;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,8 +3,8 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.util.NekosLifeApi;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,8 +3,8 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.util.NekosLifeApi;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,7 +3,7 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.NekosLifeApi;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,8 +3,8 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.util.NekosLifeApi;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -2,7 +2,7 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.util.List;

View file

@ -3,8 +3,8 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.util.NekosLifeApi;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,8 +3,8 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.util.NekosLifeApi;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -3,8 +3,8 @@ package creator.loki.command.impl.entertainment.interaction;
import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.util.NekosLifeApi;
import creator.loki.core.security.CooldownManager;
import creator.loki.integration.nekos.NekosLifeApi;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -5,7 +5,7 @@ import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.data.JsonDataStore;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -5,7 +5,7 @@ import creator.loki.command.Command;
import creator.loki.command.CommandContext;
import creator.loki.data.JsonDataStore;
import creator.loki.embed.EmbedFactory;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import net.dv8tion.jda.api.entities.Member;
import java.awt.Color;

View file

@ -16,8 +16,8 @@ import creator.loki.listener.*;
import creator.loki.metrics.MetricsCollector;
import creator.loki.profile.ProfileService;
import creator.loki.role.CustomRoleManager;
import creator.loki.util.CooldownManager;
import creator.loki.util.GlobalRateLimiter;
import creator.loki.core.security.CooldownManager;
import creator.loki.core.security.RateLimiter;
import creator.loki.voice.VoiceChannelManager;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
@ -37,7 +37,7 @@ public class BotInitializer {
private final JsonDataStore dataStore;
private final CustomRoleManager roleManager;
private final CooldownManager cooldownManager;
private final GlobalRateLimiter rateLimiter;
private final RateLimiter rateLimiter;
private final MetricsCollector metrics;
private final AudioManager audioManager;
private final LevelSystem levelSystem;
@ -66,7 +66,7 @@ public class BotInitializer {
JsonDataStore dataStore,
CustomRoleManager roleManager,
CooldownManager cooldownManager,
GlobalRateLimiter rateLimiter,
RateLimiter rateLimiter,
MetricsCollector metrics,
AudioManager audioManager,
LevelSystem levelSystem,

View file

@ -6,7 +6,7 @@ import creator.loki.config.BotConfig;
import creator.loki.embed.EmbedTemplates;
import creator.loki.exception.CommandExceptionHandler;
import creator.loki.metrics.MetricsCollector;
import creator.loki.util.GlobalRateLimiter;
import creator.loki.core.security.RateLimiter;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -22,10 +22,10 @@ public class EventDispatcher {
private final BotConfig config;
private final CommandRegistry registry;
private final ExecutorService executorService;
private final GlobalRateLimiter rateLimiter;
private final RateLimiter rateLimiter;
private final MetricsCollector metrics;
public EventDispatcher(BotConfig config, CommandRegistry registry, GlobalRateLimiter rateLimiter, MetricsCollector metrics) {
public EventDispatcher(BotConfig config, CommandRegistry registry, RateLimiter rateLimiter, MetricsCollector metrics) {
this.config = config;
this.registry = registry;
this.executorService = Executors.newCachedThreadPool();

View file

@ -13,7 +13,7 @@ import creator.loki.level.LevelSystem;
import creator.loki.metrics.MetricsCollector;
import creator.loki.profile.ProfileService;
import creator.loki.role.CustomRoleManager;
import creator.loki.util.CooldownManager;
import creator.loki.core.security.CooldownManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View file

@ -1,4 +1,4 @@
package creator.loki.util;
package creator.loki.core.security;
import java.time.Instant;
import java.util.concurrent.ConcurrentHashMap;

View file

@ -1,4 +1,4 @@
package creator.loki.util;
package creator.loki.core.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -10,11 +10,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Глобальный rate limiter для защиты от спама
* Rate limiter для защиты от спама
* Использует алгоритм Token Bucket
*/
public class GlobalRateLimiter {
private static final Logger logger = LoggerFactory.getLogger(GlobalRateLimiter.class);
public class RateLimiter {
private static final Logger logger = LoggerFactory.getLogger(RateLimiter.class);
// Лимиты по умолчанию
private static final int DEFAULT_USER_CAPACITY = 10; // Максимум токенов на пользователя
@ -26,10 +26,10 @@ public class GlobalRateLimiter {
private final ConcurrentHashMap<String, TokenBucket> guildBuckets = new ConcurrentHashMap<>();
private final ScheduledExecutorService cleanupExecutor = Executors.newSingleThreadScheduledExecutor();
public GlobalRateLimiter() {
public RateLimiter() {
// Очистка старых bucket'ов каждые 5 минут
cleanupExecutor.scheduleAtFixedRate(this::cleanup, 5, 5, TimeUnit.MINUTES);
logger.info("GlobalRateLimiter инициализирован");
logger.info("RateLimiter инициализирован");
}
/**
@ -90,7 +90,7 @@ public class GlobalRateLimiter {
* Завершает работу rate limiter
*/
public void shutdown() {
logger.info("Завершение работы GlobalRateLimiter...");
logger.info("Завершение работы RateLimiter...");
cleanupExecutor.shutdown();
try {
if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
@ -100,7 +100,7 @@ public class GlobalRateLimiter {
cleanupExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
logger.info("GlobalRateLimiter завершён");
logger.info("RateLimiter завершён");
}
/**

View file

@ -8,6 +8,9 @@ import creator.loki.config.ConfigLoader;
import creator.loki.core.CommandRegistry;
import creator.loki.core.EventDispatcher;
import creator.loki.core.SlashCommandRegistry;
import creator.loki.core.security.CooldownManager;
import creator.loki.core.security.RateLimiter;
import creator.loki.counter.CounterManager;
import creator.loki.data.JsonDataStore;
import creator.loki.database.ConfigRepository;
import creator.loki.database.Database;
@ -18,11 +21,10 @@ import creator.loki.listener.LevelUpNotifier;
import creator.loki.listener.VoiceTracker;
import creator.loki.metrics.MetricsCollector;
import creator.loki.profile.ProfileService;
import creator.loki.repository.UserStatsRepository;
import creator.loki.repository.impl.JsonUserStatsRepository;
import creator.loki.role.CustomRoleManager;
import creator.loki.util.CooldownManager;
import creator.loki.util.GlobalRateLimiter;
import creator.loki.voice.VoiceChannelManager;
import creator.loki.counter.CounterManager;
/**
* Главный модуль Guice для конфигурации Dependency Injection
@ -35,7 +37,7 @@ public class BotModule extends AbstractModule {
bind(CommandRegistry.class).in(Singleton.class);
bind(SlashCommandRegistry.class).in(Singleton.class);
bind(CooldownManager.class).in(Singleton.class);
bind(GlobalRateLimiter.class).in(Singleton.class);
bind(RateLimiter.class).in(Singleton.class);
bind(MetricsCollector.class).in(Singleton.class);
}
@ -82,7 +84,7 @@ public class BotModule extends AbstractModule {
EventDispatcher provideEventDispatcher(
BotConfig config,
CommandRegistry registry,
GlobalRateLimiter rateLimiter,
RateLimiter rateLimiter,
MetricsCollector metrics
) {
return new EventDispatcher(config, registry, rateLimiter, metrics);
@ -123,6 +125,12 @@ public class BotModule extends AbstractModule {
return new VoiceChannelManager(dataStore, configRepo);
}
@Provides
@Singleton
UserStatsRepository provideUserStatsRepository(JsonDataStore dataStore) {
return new JsonUserStatsRepository(dataStore);
}
@Provides
@Singleton
CounterManager provideCounterManager(JsonDataStore dataStore, ConfigRepository configRepo) {

View file

@ -1,4 +1,4 @@
package creator.loki.util;
package creator.loki.integration.nekos;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

View file

@ -3,7 +3,7 @@ package creator.loki.listener;
import creator.loki.core.SlashCommandRegistry;
import creator.loki.exception.CommandExceptionHandler;
import creator.loki.metrics.MetricsCollector;
import creator.loki.util.GlobalRateLimiter;
import creator.loki.core.security.RateLimiter;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.slf4j.Logger;
@ -15,10 +15,10 @@ import org.slf4j.LoggerFactory;
public class SlashCommandListener extends ListenerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SlashCommandListener.class);
private final SlashCommandRegistry registry;
private final GlobalRateLimiter rateLimiter;
private final RateLimiter rateLimiter;
private final MetricsCollector metrics;
public SlashCommandListener(SlashCommandRegistry registry, GlobalRateLimiter rateLimiter, MetricsCollector metrics) {
public SlashCommandListener(SlashCommandRegistry registry, RateLimiter rateLimiter, MetricsCollector metrics) {
this.registry = registry;
this.rateLimiter = rateLimiter;
this.metrics = metrics;

View file

@ -0,0 +1,32 @@
package creator.loki.repository;
import creator.loki.level.UserStats;
import java.util.Map;
import java.util.Optional;
/**
* Repository для работы с статистикой пользователей
*/
public interface UserStatsRepository {
/**
* Найти статистику пользователя по ID
*/
Optional<UserStats> findById(String userId);
/**
* Сохранить статистику пользователя
*/
void save(UserStats stats);
/**
* Получить всю статистику (для лидерборда)
*/
Map<String, UserStats> findAll();
/**
* Проверить существование пользователя
*/
boolean exists(String userId);
}

View file

@ -0,0 +1,50 @@
package creator.loki.repository.impl;
import com.google.gson.reflect.TypeToken;
import creator.loki.data.JsonDataStore;
import creator.loki.level.UserStats;
import creator.loki.repository.UserStatsRepository;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* Реализация UserStatsRepository через JsonDataStore
*/
public class JsonUserStatsRepository implements UserStatsRepository {
private static final String DATA_KEY = "userStats";
private static final Type TYPE = new TypeToken<Map<String, UserStats>>(){}.getType();
private final JsonDataStore dataStore;
public JsonUserStatsRepository(JsonDataStore dataStore) {
this.dataStore = dataStore;
}
@Override
public Optional<UserStats> findById(String userId) {
Map<String, UserStats> allStats = findAll();
return Optional.ofNullable(allStats.get(userId));
}
@Override
public void save(UserStats stats) {
Map<String, UserStats> allStats = findAll();
allStats.put(stats.getUserId(), stats);
dataStore.setData(DATA_KEY, allStats);
}
@Override
public Map<String, UserStats> findAll() {
Map<String, UserStats> stats = dataStore.getData(DATA_KEY, TYPE);
return stats != null ? stats : new HashMap<>();
}
@Override
public boolean exists(String userId) {
return findAll().containsKey(userId);
}
}

View file

@ -6,12 +6,12 @@ import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;
class GlobalRateLimiterTest {
private GlobalRateLimiter rateLimiter;
class RateLimiterTest {
private RateLimiter rateLimiter;
@BeforeEach
void setUp() {
rateLimiter = new GlobalRateLimiter();
rateLimiter = new RateLimiter();
}
@AfterEach