Player name caching

This commit is contained in:
Braydon 2024-04-06 15:44:00 -04:00
parent aaee6c8ce9
commit fd44a0b25c
5 changed files with 74 additions and 8 deletions

@ -2,7 +2,6 @@ package me.braydon.mc.model;
import lombok.*; import lombok.*;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import java.util.UUID; import java.util.UUID;
@ -16,7 +15,6 @@ import java.util.UUID;
@Setter @Getter @Setter @Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString @ToString
@RedisHash(value = "player", timeToLive = 60L * 60L) // 1 hour (in seconds)
public class Player { public class Player {
/** /**
* The unique id of this player. * The unique id of this player.

@ -3,8 +3,10 @@ package me.braydon.mc.model.cache;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
import me.braydon.mc.model.Player; import me.braydon.mc.model.Player;
import me.braydon.mc.model.ProfileAction; import me.braydon.mc.model.ProfileAction;
import org.springframework.data.redis.core.RedisHash;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID; import java.util.UUID;
@ -15,6 +17,8 @@ import java.util.UUID;
* @author Braydon * @author Braydon
*/ */
@Setter @Getter @Setter @Getter
@ToString(callSuper = true)
@RedisHash(value = "player", timeToLive = 60L * 60L) // 1 hour (in seconds)
public final class CachedPlayer extends Player implements Serializable { public final class CachedPlayer extends Player implements Serializable {
/** /**
* The unix timestamp of when this * The unix timestamp of when this

@ -0,0 +1,27 @@
package me.braydon.mc.model.cache;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import java.util.UUID;
/**
* @author Braydon
*/
@AllArgsConstructor
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
@RedisHash(value = "playerName", timeToLive = 60L * 60L) // 1 hour (in seconds)
public final class CachedPlayerName {
/**
* The username of the player.
*/
@Id @NonNull private String username;
/**
* The unique id of the player.
*/
@NonNull private UUID uniqueId;
}

@ -0,0 +1,15 @@
package me.braydon.mc.repository;
import me.braydon.mc.model.cache.CachedPlayerName;
import org.springframework.data.repository.CrudRepository;
/**
* A cache repository for player usernames.
* <p>
* This will allow us to easily lookup a
* player's username and get their uuid.
* </p>
*
* @author Braydon
*/
public interface PlayerNameCacheRepository extends CrudRepository<CachedPlayerName, String> { }

@ -10,9 +10,11 @@ import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.Player; import me.braydon.mc.model.Player;
import me.braydon.mc.model.ProfileAction; import me.braydon.mc.model.ProfileAction;
import me.braydon.mc.model.cache.CachedPlayer; import me.braydon.mc.model.cache.CachedPlayer;
import me.braydon.mc.model.cache.CachedPlayerName;
import me.braydon.mc.model.token.MojangProfileToken; import me.braydon.mc.model.token.MojangProfileToken;
import me.braydon.mc.model.token.MojangUsernameToUUIDToken; import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
import me.braydon.mc.repository.PlayerCacheRepository; import me.braydon.mc.repository.PlayerCacheRepository;
import me.braydon.mc.repository.PlayerNameCacheRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -33,14 +35,20 @@ public final class MojangService {
private static final String UUID_TO_PROFILE = SESSION_SERVER_ENDPOINT + "/session/minecraft/profile/%s"; private static final String UUID_TO_PROFILE = SESSION_SERVER_ENDPOINT + "/session/minecraft/profile/%s";
private static final String USERNAME_TO_UUID = API_ENDPOINT + "/users/profiles/minecraft/%s"; private static final String USERNAME_TO_UUID = API_ENDPOINT + "/users/profiles/minecraft/%s";
/**
* The cache repository for {@link Player}'s by their username.
*/
@NonNull private final PlayerNameCacheRepository playerNameCache;
/** /**
* The cache repository for {@link Player}'s. * The cache repository for {@link Player}'s.
*/ */
@NonNull private final PlayerCacheRepository playerCacheRepository; @NonNull private final PlayerCacheRepository playerCache;
@Autowired @Autowired
public MojangService(@NonNull PlayerCacheRepository playerCacheRepository) { public MojangService(@NonNull PlayerNameCacheRepository playerNameCache, @NonNull PlayerCacheRepository playerCache) {
this.playerCacheRepository = playerCacheRepository; this.playerNameCache = playerNameCache;
this.playerCache = playerCache;
} }
/** /**
@ -76,7 +84,7 @@ public final class MojangService {
} }
// Check the cache for the player // Check the cache for the player
Optional<CachedPlayer> cached = playerCacheRepository.findById(uuid); Optional<CachedPlayer> cached = playerCache.findById(uuid);
if (cached.isPresent()) { // Respond with the cache if present if (cached.isPresent()) { // Respond with the cache if present
log.info("Found player in cache: {}", uuid); log.info("Found player in cache: {}", uuid);
return cached.get(); return cached.get();
@ -97,7 +105,7 @@ public final class MojangService {
profileActions.length == 0 ? null : profileActions, profileActions.length == 0 ? null : profileActions,
System.currentTimeMillis() System.currentTimeMillis()
); );
playerCacheRepository.save(player); playerCache.save(player);
log.info("Cached player: {}", uuid); log.info("Cached player: {}", uuid);
player.setCached(-1L); // Set to -1 to indicate it's not cached in the response player.setCached(-1L); // Set to -1 to indicate it's not cached in the response
@ -120,12 +128,26 @@ public final class MojangService {
*/ */
@NonNull @NonNull
private UUID usernameToUUID(@NonNull String username) throws ResourceNotFoundException { private UUID usernameToUUID(@NonNull String username) throws ResourceNotFoundException {
username = username.toLowerCase(); // Lowercase the username
// Check the cache for the player's UUID
Optional<CachedPlayerName> cached = playerNameCache.findById(username);
if (cached.isPresent()) { // Respond with the cache if present
log.info("Found UUID in cache for username {}: {}", username, cached.get().getUniqueId());
return cached.get().getUniqueId();
}
// Make a request to Mojang requesting the UUID // Make a request to Mojang requesting the UUID
try { try {
MojangUsernameToUUIDToken token = JsonWebRequest.makeRequest( MojangUsernameToUUIDToken token = JsonWebRequest.makeRequest(
USERNAME_TO_UUID.formatted(username), HttpMethod.GET USERNAME_TO_UUID.formatted(username), HttpMethod.GET
).execute(MojangUsernameToUUIDToken.class); ).execute(MojangUsernameToUUIDToken.class);
return UUIDUtils.addDashes(token.getId()); // Return the UUID with dashes
// Cache the UUID and return it
UUID uuid = UUIDUtils.addDashes(token.getId());
playerNameCache.save(new CachedPlayerName(username, uuid));
log.info("Cached UUID for username {}: {}", username, uuid);
return uuid;
} catch (JsonWebException ex) { } catch (JsonWebException ex) {
if (ex.getStatusCode() == 404) { if (ex.getStatusCode() == 404) {
throw new ResourceNotFoundException("Player not found with username: %s".formatted(username)); throw new ResourceNotFoundException("Player not found with username: %s".formatted(username));