diff --git a/bot/src/main/java/net/lindseybot/bot/listener/UserNameListener.java b/bot/src/main/java/net/lindseybot/bot/listener/UserNameListener.java index 078a644..18e8c51 100644 --- a/bot/src/main/java/net/lindseybot/bot/listener/UserNameListener.java +++ b/bot/src/main/java/net/lindseybot/bot/listener/UserNameListener.java @@ -1,5 +1,6 @@ package net.lindseybot.bot.listener; +import jakarta.annotation.PreDestroy; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.hooks.IEventManager; import net.dv8tion.jda.api.hooks.ListenerAdapter; @@ -10,6 +11,9 @@ import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Queue; import java.util.concurrent.TimeUnit; @Component @@ -19,9 +23,10 @@ public class UserNameListener extends ListenerAdapter implements ExpirationListe private final ExpiringMap users = ExpiringMap.builder() .expirationPolicy(ExpirationPolicy.CREATED) .expiration(1, TimeUnit.MINUTES) - .asyncExpirationListener(this) + .expirationListener(this) .maxSize(15_000) .build(); + private final Queue queue = new ArrayDeque<>(); public UserNameListener(IEventManager api, ProfileServiceImpl profiles) { this.profiles = profiles; @@ -39,7 +44,22 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { @Override public void expired(Long userId, String name) { - this.profiles.updateName(userId, name); + this.queue.add(new UserUpdate(userId, name)); + if (queue.size() > 250) { + List updates = queue.stream() + .limit(250).toList(); + this.profiles.updateNames(updates); + } + } + + @PreDestroy + public void onDestroy() { + List updates = queue.stream() + .toList(); + this.profiles.updateNames(updates); + } + + public record UserUpdate(long id, String name) { } } diff --git a/bot/src/main/java/net/lindseybot/bot/repositories/sql/UserRepository.java b/bot/src/main/java/net/lindseybot/bot/repositories/sql/UserRepository.java index ddeab5f..8f9509e 100644 --- a/bot/src/main/java/net/lindseybot/bot/repositories/sql/UserRepository.java +++ b/bot/src/main/java/net/lindseybot/bot/repositories/sql/UserRepository.java @@ -9,10 +9,6 @@ public interface UserRepository extends JpaRepository { int countByUser(long id); - @Modifying - @Query("update UserProfile profile set profile.name = ?1, profile.lastSeen = ?2 where profile.user = ?3") - void updateName(String name, long seen, long user); - @Modifying @Query("update UserProfile pr set pr.cookieStreak = 0 where pr.cookieStreak > 0 and pr.lastDailyCookies < ?1") int deleteOutdatedStreaks(long timestamp); diff --git a/bot/src/main/java/net/lindseybot/bot/services/ProfileServiceImpl.java b/bot/src/main/java/net/lindseybot/bot/services/ProfileServiceImpl.java index 929f2f5..f66e7ba 100644 --- a/bot/src/main/java/net/lindseybot/bot/services/ProfileServiceImpl.java +++ b/bot/src/main/java/net/lindseybot/bot/services/ProfileServiceImpl.java @@ -3,6 +3,7 @@ import lombok.extern.slf4j.Slf4j; import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpiringMap; +import net.lindseybot.bot.listener.UserNameListener; import net.lindseybot.bot.repositories.sql.MemberRepository; import net.lindseybot.bot.repositories.sql.ServerRepository; import net.lindseybot.bot.repositories.sql.UserRepository; @@ -12,12 +13,17 @@ import net.lindseybot.shared.entities.profile.members.MemberId; import net.lindseybot.shared.worker.services.ProfileService; import org.jetbrains.annotations.NotNull; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.time.Instant; import java.time.temporal.ChronoUnit; +import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -28,6 +34,7 @@ public class ProfileServiceImpl implements ProfileService { private final UserRepository users; private final MemberRepository members; private final ServerRepository servers; + private final JdbcTemplate template; private final ExpiringMap userCache = ExpiringMap.builder() .expirationPolicy(ExpirationPolicy.ACCESSED) @@ -35,12 +42,15 @@ public class ProfileServiceImpl implements ProfileService { .maxSize(10_000) .build(); - public ProfileServiceImpl(UserRepository users, - MemberRepository members, - ServerRepository servers) { + public ProfileServiceImpl( + UserRepository users, + MemberRepository members, + ServerRepository servers, + JdbcTemplate template) { this.users = users; this.members = members; this.servers = servers; + this.template = template; } @Override @@ -117,8 +127,20 @@ public void updateSeen(Set guilds) { } @Transactional - public void updateName(long user, String name) { - this.users.updateName(name, System.currentTimeMillis(), user); + public void updateNames(List updates) { + this.template.batchUpdate("update user_settings set name = ?, last_seen = ? where user = ?", + new BatchPreparedStatementSetter() { + public void setValues(@NotNull PreparedStatement ps, int i) throws SQLException { + var update = updates.get(i); + ps.setString(1, update.name()); + ps.setLong(2, System.currentTimeMillis()); + ps.setLong(3, update.id()); + } + + public int getBatchSize() { + return updates.size(); + } + }); } } diff --git a/bot/src/main/java/net/lindseybot/bot/spring/WorkerConfig.java b/bot/src/main/java/net/lindseybot/bot/spring/WorkerConfig.java index 27d177d..c1966b0 100644 --- a/bot/src/main/java/net/lindseybot/bot/spring/WorkerConfig.java +++ b/bot/src/main/java/net/lindseybot/bot/spring/WorkerConfig.java @@ -1,6 +1,7 @@ package net.lindseybot.bot.spring; import net.lindseybot.shared.properties.BotProperties; +import net.lindseybot.shared.services.CacheService; import net.lindseybot.shared.worker.impl.MessengerImpl; import net.lindseybot.shared.worker.services.DiscordAdapter; import net.lindseybot.shared.worker.services.Messenger; @@ -34,4 +35,9 @@ public Translator translator(ProfileService profiles) { return new Translator(profiles); } + @Bean + public CacheService cacheService() { + return new CacheService(); + } + } diff --git a/common/src/main/java/net/lindseybot/shared/services/CacheService.java b/common/src/main/java/net/lindseybot/shared/services/CacheService.java new file mode 100644 index 0000000..bb4644f --- /dev/null +++ b/common/src/main/java/net/lindseybot/shared/services/CacheService.java @@ -0,0 +1,40 @@ +package net.lindseybot.shared.services; + +import lombok.Getter; +import net.jodah.expiringmap.ExpirationPolicy; +import net.jodah.expiringmap.ExpiringMap; +import net.lindseybot.shared.entities.profile.servers.AntiAd; +import net.lindseybot.shared.entities.profile.servers.AntiScam; +import net.lindseybot.shared.entities.profile.servers.Registration; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Getter +public class CacheService { + + private final Map roleHistory = ExpiringMap.builder() + .expirationPolicy(ExpirationPolicy.ACCESSED) + .expiration(10, TimeUnit.MINUTES) + .maxSize(50_000) + .build(); + + private final Map antiScam = ExpiringMap.builder() + .expirationPolicy(ExpirationPolicy.ACCESSED) + .expiration(10, TimeUnit.MINUTES) + .maxSize(50_000) + .build(); + + private final Map antiAd = ExpiringMap.builder() + .expirationPolicy(ExpirationPolicy.ACCESSED) + .expiration(30, TimeUnit.MINUTES) + .maxSize(50_000) + .build(); + + private final Map registration = ExpiringMap.builder() + .expirationPolicy(ExpirationPolicy.ACCESSED) + .expiration(30, TimeUnit.MINUTES) + .maxSize(50_000) + .build(); + +} diff --git a/module-automod/src/main/java/net/lindseybot/automod/services/AntiAdService.java b/module-automod/src/main/java/net/lindseybot/automod/services/AntiAdService.java index ddc641c..ab92e92 100644 --- a/module-automod/src/main/java/net/lindseybot/automod/services/AntiAdService.java +++ b/module-automod/src/main/java/net/lindseybot/automod/services/AntiAdService.java @@ -3,20 +3,28 @@ import net.dv8tion.jda.api.entities.Guild; import net.lindseybot.automod.repositories.sql.AntiAdRepository; import net.lindseybot.shared.entities.profile.servers.AntiAd; +import net.lindseybot.shared.services.CacheService; import org.springframework.stereotype.Service; @Service public class AntiAdService { private final AntiAdRepository repository; + private final CacheService cache; - public AntiAdService(AntiAdRepository repository) { + public AntiAdService(AntiAdRepository repository, CacheService cache) { this.repository = repository; + this.cache = cache; } public AntiAd find(Guild guild) { - return repository.findById(guild.getIdLong()) + if (this.cache.getAntiAd().containsKey(guild.getIdLong())) { + return this.cache.getAntiAd().get(guild.getIdLong()); + } + var data = repository.findById(guild.getIdLong()) .orElse(new AntiAd(guild.getIdLong())); + this.cache.getAntiAd().put(guild.getIdLong(), data); + return data; } } diff --git a/module-automod/src/main/java/net/lindseybot/automod/services/AntiScamService.java b/module-automod/src/main/java/net/lindseybot/automod/services/AntiScamService.java index fd0683c..bfa9675 100644 --- a/module-automod/src/main/java/net/lindseybot/automod/services/AntiScamService.java +++ b/module-automod/src/main/java/net/lindseybot/automod/services/AntiScamService.java @@ -3,20 +3,28 @@ import net.dv8tion.jda.api.entities.Guild; import net.lindseybot.automod.repositories.sql.AntiScamRepository; import net.lindseybot.shared.entities.profile.servers.AntiScam; +import net.lindseybot.shared.services.CacheService; import org.springframework.stereotype.Service; @Service public class AntiScamService { private final AntiScamRepository repository; + private final CacheService cache; - public AntiScamService(AntiScamRepository repository) { + public AntiScamService(AntiScamRepository repository, CacheService cache) { this.repository = repository; + this.cache = cache; } public AntiScam find(Guild guild) { - return repository.findById(guild.getIdLong()) + if (this.cache.getAntiScam().containsKey(guild.getIdLong())) { + return this.cache.getAntiScam().get(guild.getIdLong()); + } + var data = repository.findById(guild.getIdLong()) .orElse(new AntiScam(guild.getIdLong())); + this.cache.getAntiScam().put(guild.getIdLong(), data); + return data; } } diff --git a/module-automod/src/main/java/net/lindseybot/automod/services/RegistrationService.java b/module-automod/src/main/java/net/lindseybot/automod/services/RegistrationService.java index 6ad6c9f..364a795 100644 --- a/module-automod/src/main/java/net/lindseybot/automod/services/RegistrationService.java +++ b/module-automod/src/main/java/net/lindseybot/automod/services/RegistrationService.java @@ -1,43 +1,36 @@ package net.lindseybot.automod.services; import net.dv8tion.jda.api.entities.Guild; -import net.jodah.expiringmap.ExpirationPolicy; -import net.jodah.expiringmap.ExpiringMap; import net.lindseybot.automod.repositories.sql.RegistrationRepository; import net.lindseybot.shared.entities.profile.servers.Registration; +import net.lindseybot.shared.services.CacheService; import org.springframework.stereotype.Service; -import java.util.concurrent.TimeUnit; - @Service public class RegistrationService { private final RegistrationRepository repository; - private final ExpiringMap cache = ExpiringMap.builder() - .expirationPolicy(ExpirationPolicy.ACCESSED) - .expiration(1, TimeUnit.MINUTES) - .maxSize(10_000) - .build(); + private final CacheService cache; - public RegistrationService(RegistrationRepository repository) { + public RegistrationService(RegistrationRepository repository, CacheService cache) { this.repository = repository; + this.cache = cache; } public Registration find(Guild guild) { - Registration cached = this.cache.get(guild.getIdLong()); - if (cached != null) { - return cached; + if (this.cache.getRegistration().containsKey(guild.getIdLong())) { + return this.cache.getRegistration().get(guild.getIdLong()); } - Registration registration = repository.findById(guild.getIdLong()) + var data = repository.findById(guild.getIdLong()) .orElse(new Registration(guild.getIdLong())); - this.cache.put(guild.getIdLong(), registration); - return registration; + this.cache.getRegistration().put(guild.getIdLong(), data); + return data; } public void disable(Registration registration) { registration.setEnabled(false); this.repository.save(registration); - this.cache.remove(registration.getGuild()); + this.cache.getRegistration().remove(registration.getGuild()); } } diff --git a/module-automod/src/main/java/net/lindseybot/automod/services/RoleHistoryService.java b/module-automod/src/main/java/net/lindseybot/automod/services/RoleHistoryService.java index 62a4188..d5aaf0e 100644 --- a/module-automod/src/main/java/net/lindseybot/automod/services/RoleHistoryService.java +++ b/module-automod/src/main/java/net/lindseybot/automod/services/RoleHistoryService.java @@ -8,6 +8,7 @@ import net.lindseybot.shared.entities.profile.members.MemberId; import net.lindseybot.shared.entities.profile.members.RoleHistory; import net.lindseybot.shared.entities.profile.servers.KeepRoles; +import net.lindseybot.shared.services.CacheService; import net.lindseybot.shared.worker.services.NotificationService; import org.springframework.stereotype.Service; @@ -17,19 +18,28 @@ public class RoleHistoryService { private final KeepRolesRepository repository; private final RoleHistoryRepository history; private final NotificationService notifications; + private final CacheService cache; - public RoleHistoryService(KeepRolesRepository repository, - RoleHistoryRepository history, - NotificationService notifications) { + public RoleHistoryService( + KeepRolesRepository repository, + RoleHistoryRepository history, + NotificationService notifications, + CacheService cache) { this.repository = repository; this.history = history; this.notifications = notifications; + this.cache = cache; } public boolean isActive(Guild guild) { - return this.repository.findById(guild.getIdLong()) + if (this.cache.getRoleHistory().containsKey(guild.getIdLong())) { + return this.cache.getRoleHistory().get(guild.getIdLong()); + } + boolean active = this.repository.findById(guild.getIdLong()) .orElse(new KeepRoles()) .isEnabled(); + this.cache.getRoleHistory().put(guild.getIdLong(), active); + return active; } public RoleHistory findByMember(Member member) { @@ -52,6 +62,7 @@ public void disable(Guild guild, Label keepRoles) { } config.setEnabled(false); this.repository.save(config); + this.cache.getRoleHistory().remove(guild.getIdLong()); this.notifications.notify(guild, keepRoles); } diff --git a/module-help/src/main/java/net/lindseybot/help/handlers/AntiAdHandler.java b/module-help/src/main/java/net/lindseybot/help/handlers/AntiAdHandler.java index ca6920a..855c9d1 100644 --- a/module-help/src/main/java/net/lindseybot/help/handlers/AntiAdHandler.java +++ b/module-help/src/main/java/net/lindseybot/help/handlers/AntiAdHandler.java @@ -9,6 +9,7 @@ import net.lindseybot.shared.entities.discord.builders.ButtonBuilder; import net.lindseybot.shared.entities.discord.builders.MessageBuilder; import net.lindseybot.shared.entities.profile.servers.AntiAd; +import net.lindseybot.shared.services.CacheService; import net.lindseybot.shared.worker.InteractionHandler; import net.lindseybot.shared.worker.services.Messenger; import org.springframework.stereotype.Component; @@ -17,10 +18,12 @@ public class AntiAdHandler extends InteractionHandler implements ModuleHandler { private final HelpAntiAdService service; + private final CacheService cache; - public AntiAdHandler(Messenger msg, HelpAntiAdService service) { + public AntiAdHandler(Messenger msg, HelpAntiAdService service, CacheService cache) { super(msg); this.service = service; + this.cache = cache; } @Override @@ -43,6 +46,7 @@ public FMessage enable(Member member, Guild guild) { AntiAd antiAd = this.service.get(guild); antiAd.setEnabled(true); this.service.save(antiAd); + this.cache.getAntiAd().remove(guild.getIdLong()); return this.onStatus(member, guild); } @@ -51,6 +55,7 @@ public FMessage disable(Member member, Guild guild) { AntiAd antiAd = this.service.get(guild); antiAd.setEnabled(false); this.service.save(antiAd); + this.cache.getAntiAd().remove(guild.getIdLong()); return this.onStatus(member, guild); } diff --git a/module-help/src/main/java/net/lindseybot/help/handlers/AntiScamHandler.java b/module-help/src/main/java/net/lindseybot/help/handlers/AntiScamHandler.java index f9be2ae..2b85c38 100644 --- a/module-help/src/main/java/net/lindseybot/help/handlers/AntiScamHandler.java +++ b/module-help/src/main/java/net/lindseybot/help/handlers/AntiScamHandler.java @@ -10,6 +10,7 @@ import net.lindseybot.shared.entities.discord.builders.ButtonBuilder; import net.lindseybot.shared.entities.discord.builders.MessageBuilder; import net.lindseybot.shared.entities.profile.servers.AntiScam; +import net.lindseybot.shared.services.CacheService; import net.lindseybot.shared.worker.InteractionHandler; import net.lindseybot.shared.worker.services.Messenger; import org.springframework.stereotype.Component; @@ -18,10 +19,12 @@ public class AntiScamHandler extends InteractionHandler implements ModuleHandler { private final HelpAntiScamService service; + private final CacheService cache; - public AntiScamHandler(Messenger msg, HelpAntiScamService service) { + public AntiScamHandler(Messenger msg, HelpAntiScamService service, CacheService cache) { super(msg); this.service = service; + this.cache = cache; } @Override @@ -48,6 +51,7 @@ public FMessage enable(Member member, Guild guild) { AntiScam antiScam = this.service.get(guild); antiScam.setEnabled(true); this.service.save(antiScam); + this.cache.getAntiScam().remove(guild.getIdLong()); return this.onStatus(member, guild); } @@ -56,6 +60,7 @@ public FMessage disable(Member member, Guild guild) { AntiScam antiScam = this.service.get(guild); antiScam.setEnabled(false); this.service.save(antiScam); + this.cache.getAntiScam().remove(guild.getIdLong()); return this.onStatus(member, guild); } diff --git a/module-help/src/main/java/net/lindseybot/help/handlers/KeepRolesHandler.java b/module-help/src/main/java/net/lindseybot/help/handlers/KeepRolesHandler.java index c5643b6..628ca48 100644 --- a/module-help/src/main/java/net/lindseybot/help/handlers/KeepRolesHandler.java +++ b/module-help/src/main/java/net/lindseybot/help/handlers/KeepRolesHandler.java @@ -10,6 +10,7 @@ import net.lindseybot.shared.entities.discord.builders.ButtonBuilder; import net.lindseybot.shared.entities.discord.builders.MessageBuilder; import net.lindseybot.shared.entities.profile.servers.KeepRoles; +import net.lindseybot.shared.services.CacheService; import net.lindseybot.shared.worker.InteractionHandler; import net.lindseybot.shared.worker.services.Messenger; import org.springframework.stereotype.Component; @@ -18,10 +19,15 @@ public class KeepRolesHandler extends InteractionHandler implements ModuleHandler { private final HelpKeepRolesService service; + private final CacheService cache; - public KeepRolesHandler(Messenger msg, HelpKeepRolesService service) { + public KeepRolesHandler( + Messenger msg, + HelpKeepRolesService service, + CacheService cache) { super(msg); this.service = service; + this.cache = cache; } @Override @@ -48,6 +54,7 @@ public FMessage enable(Member member, Guild guild) { KeepRoles keepRoles = this.service.get(guild); keepRoles.setEnabled(true); this.service.save(keepRoles); + this.cache.getRoleHistory().remove(guild.getIdLong()); return this.onStatus(member, guild); } @@ -56,6 +63,7 @@ public FMessage disable(Member member, Guild guild) { KeepRoles keepRoles = this.service.get(guild); keepRoles.setEnabled(false); this.service.save(keepRoles); + this.cache.getRoleHistory().remove(guild.getIdLong()); return this.onStatus(member, guild); } diff --git a/module-help/src/main/java/net/lindseybot/help/handlers/RegisterHandler.java b/module-help/src/main/java/net/lindseybot/help/handlers/RegisterHandler.java index cb0aaeb..23b6c08 100644 --- a/module-help/src/main/java/net/lindseybot/help/handlers/RegisterHandler.java +++ b/module-help/src/main/java/net/lindseybot/help/handlers/RegisterHandler.java @@ -18,6 +18,7 @@ import net.lindseybot.shared.entities.discord.builders.MessageBuilder; import net.lindseybot.shared.entities.discord.builders.SelectMenuBuilder; import net.lindseybot.shared.entities.profile.servers.Registration; +import net.lindseybot.shared.services.CacheService; import net.lindseybot.shared.utils.GFXUtils; import net.lindseybot.shared.worker.Button; import net.lindseybot.shared.worker.InteractionHandler; @@ -31,10 +32,12 @@ public class RegisterHandler extends InteractionHandler implements ModuleHandler { private final HelpRegisterService service; + private final CacheService cache; - public RegisterHandler(Messenger msg, HelpRegisterService service) { + public RegisterHandler(Messenger msg, HelpRegisterService service, CacheService cache) { super(msg); this.service = service; + this.cache = cache; } @Override @@ -57,6 +60,7 @@ public FMessage enable(Member member, Guild guild) { Registration registration = this.service.get(guild); registration.setEnabled(true); this.service.save(registration); + this.cache.getRegistration().remove(guild.getIdLong()); return this.onStatus(member, guild); } @@ -65,6 +69,7 @@ public FMessage disable(Member member, Guild guild) { Registration registration = this.service.get(guild); registration.setEnabled(false); this.service.save(registration); + this.cache.getRegistration().remove(guild.getIdLong()); return this.onStatus(member, guild); } @@ -77,6 +82,7 @@ public FMessage onStatus(Member member, Guild guild) { if (channel == null) { registration.setEnabled(false); this.service.save(registration); + this.cache.getRegistration().remove(guild.getIdLong()); return this.onStatus(member, guild); } builder.content(Label.of("commands.modules.register.enabled", channel.getAsMention())); @@ -143,6 +149,7 @@ public void onStep1(StringSelectInteractionEvent event) { Registration registration = this.service.get(event.getGuild()); registration.setChannelId(id); this.service.save(registration); + this.cache.getRegistration().remove(event.getGuild().getIdLong()); EmbedBuilder embed = new EmbedBuilder(); embed.title(Label.raw("Registration Setup (2/3)")); @@ -183,11 +190,12 @@ public void onStep2(StringSelectInteractionEvent event) { Registration registration = this.service.get(event.getGuild()); registration.setRoleId(id); this.service.save(registration); + this.cache.getRegistration().remove(event.getGuild().getIdLong()); EmbedBuilder embed = new EmbedBuilder(); embed.title(Label.raw("Registration Setup (3/3)")); embed.description(Label.raw("Please type the desired register phrase/word in chat, and when you are done click the Finish button below. Remember you can use placeholders to fill" + - " information like the user's name.")); + " information like the user's name.")); embed.color(GFXUtils.BLUE); MessageBuilder builder = new MessageBuilder(); @@ -233,6 +241,7 @@ public void onStep3(ButtonInteractionEvent event) { Registration registration = this.service.get(event.getGuild()); registration.setPhrase(content); this.service.save(registration); + this.cache.getRegistration().remove(event.getGuild().getIdLong()); this.msg.edit(event, this.onStatus(event.getMember(), event.getGuild())); message.delete()