Add player caching
This commit is contained in:
parent
e146fa1030
commit
f9426395a0
16
pom.xml
16
pom.xml
@ -41,6 +41,22 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis for caching -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.lettuce</groupId>
|
||||
<artifactId>lettuce-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Libraries -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
|
@ -5,8 +5,13 @@ import com.google.gson.GsonBuilder;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
@ -23,6 +28,30 @@ public class RESTfulMC {
|
||||
.serializeNulls()
|
||||
.create();
|
||||
|
||||
/**
|
||||
* The Redis server host.
|
||||
*/
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String redisHost;
|
||||
|
||||
/**
|
||||
* The Redis server port.
|
||||
*/
|
||||
@Value("${spring.data.redis.port}")
|
||||
private int redisPort;
|
||||
|
||||
/**
|
||||
* The Redis database index.
|
||||
*/
|
||||
@Value("${spring.data.redis.database}")
|
||||
private int redisDatabase;
|
||||
|
||||
/**
|
||||
* The optional Redis password.
|
||||
*/
|
||||
@Value("${spring.data.redis.auth}")
|
||||
private String redisAuth;
|
||||
|
||||
@SneakyThrows
|
||||
public static void main(@NonNull String[] args) {
|
||||
// Handle loading of our configuration file
|
||||
@ -39,4 +68,34 @@ public class RESTfulMC {
|
||||
// Start the app
|
||||
SpringApplication.run(RESTfulMC.class, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the config to use for Redis.
|
||||
*
|
||||
* @return the config
|
||||
* @see RedisTemplate for config
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public RedisTemplate<String, Object> redisTemplate() {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(jedisConnectionFactory());
|
||||
return template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the connection factory to use
|
||||
* when making connections to Redis.
|
||||
*
|
||||
* @return the built factory
|
||||
* @see JedisConnectionFactory for factory
|
||||
*/
|
||||
@Bean @NonNull
|
||||
public JedisConnectionFactory jedisConnectionFactory() {
|
||||
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
|
||||
config.setDatabase(redisDatabase);
|
||||
if (!redisAuth.trim().isEmpty()) { // Auth with our provided password
|
||||
config.setPassword(redisAuth);
|
||||
}
|
||||
return new JedisConnectionFactory(config);
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import lombok.NonNull;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
||||
import me.braydon.mc.model.Player;
|
||||
import me.braydon.mc.model.cache.CachedPlayer;
|
||||
import me.braydon.mc.service.MojangService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
@ -41,7 +41,7 @@ public final class PlayerController {
|
||||
*/
|
||||
@GetMapping("/{query}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<Player> getPlayer(@PathVariable @NonNull String query) throws BadRequestException, ResourceNotFoundException {
|
||||
public ResponseEntity<CachedPlayer> getPlayer(@PathVariable @NonNull String query) throws BadRequestException, ResourceNotFoundException {
|
||||
return ResponseEntity.ofNullable(mojangService.getPlayer(query));
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package me.braydon.mc.exception;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.braydon.mc.model.ErrorResponse;
|
||||
import me.braydon.mc.model.response.ErrorResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
|
@ -1,6 +1,8 @@
|
||||
package me.braydon.mc.model;
|
||||
|
||||
import lombok.*;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@ -9,12 +11,17 @@ import java.util.UUID;
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@NoArgsConstructor @AllArgsConstructor @Setter @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||
public final class Player {
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Setter @Getter
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
@ToString
|
||||
@RedisHash(value = "player", timeToLive = 60L * 60L) // 1 hour (in seconds)
|
||||
public class Player {
|
||||
/**
|
||||
* The unique id of this player.
|
||||
*/
|
||||
@EqualsAndHashCode.Include @NonNull private UUID uniqueId;
|
||||
@Id @EqualsAndHashCode.Include @NonNull private UUID uniqueId;
|
||||
|
||||
/**
|
||||
* The username of this player.
|
||||
@ -22,7 +29,7 @@ public final class Player {
|
||||
@NonNull private String username;
|
||||
|
||||
/**
|
||||
* The profile actions this player has.
|
||||
* The profile actions this player has, null if none.
|
||||
*/
|
||||
@NonNull private ProfileAction[] profileActions;
|
||||
private ProfileAction[] profileActions;
|
||||
}
|
29
src/main/java/me/braydon/mc/model/cache/CachedPlayer.java
vendored
Normal file
29
src/main/java/me/braydon/mc/model/cache/CachedPlayer.java
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
package me.braydon.mc.model.cache;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.braydon.mc.model.Player;
|
||||
import me.braydon.mc.model.ProfileAction;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A cacheable {@link Player}.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
@Setter @Getter
|
||||
public final class CachedPlayer extends Player implements Serializable {
|
||||
/**
|
||||
* The unix timestamp of when this
|
||||
* player was cached, -1 if not cached.
|
||||
*/
|
||||
private long cached;
|
||||
|
||||
public CachedPlayer(@NonNull UUID uniqueId, @NonNull String username, ProfileAction[] profileActions, long cached) {
|
||||
super(uniqueId, username, profileActions);
|
||||
this.cached = cached;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package me.braydon.mc.model;
|
||||
package me.braydon.mc.model.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.*;
|
@ -0,0 +1,14 @@
|
||||
package me.braydon.mc.repository;
|
||||
|
||||
import me.braydon.mc.model.Player;
|
||||
import me.braydon.mc.model.cache.CachedPlayer;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* A cache repository for {@link Player}'s.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface PlayerCacheRepository extends CrudRepository<CachedPlayer, UUID> { }
|
@ -8,11 +8,16 @@ import me.braydon.mc.common.web.JsonWebRequest;
|
||||
import me.braydon.mc.exception.impl.BadRequestException;
|
||||
import me.braydon.mc.exception.impl.ResourceNotFoundException;
|
||||
import me.braydon.mc.model.Player;
|
||||
import me.braydon.mc.model.ProfileAction;
|
||||
import me.braydon.mc.model.cache.CachedPlayer;
|
||||
import me.braydon.mc.model.token.MojangProfileToken;
|
||||
import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
|
||||
import me.braydon.mc.repository.PlayerCacheRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -28,6 +33,16 @@ public final class MojangService {
|
||||
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";
|
||||
|
||||
/**
|
||||
* The cache repository for {@link Player}'s.
|
||||
*/
|
||||
@NonNull private final PlayerCacheRepository playerCacheRepository;
|
||||
|
||||
@Autowired
|
||||
public MojangService(@NonNull PlayerCacheRepository playerCacheRepository) {
|
||||
this.playerCacheRepository = playerCacheRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a player by their username or UUID.
|
||||
*
|
||||
@ -37,7 +52,7 @@ public final class MojangService {
|
||||
* @throws ResourceNotFoundException if the player is not found
|
||||
*/
|
||||
@NonNull
|
||||
public Player getPlayer(@NonNull String query) throws BadRequestException, ResourceNotFoundException {
|
||||
public CachedPlayer getPlayer(@NonNull String query) throws BadRequestException, ResourceNotFoundException {
|
||||
log.info("Requesting player with query: {}", query);
|
||||
|
||||
UUID uuid; // The player UUID to lookup
|
||||
@ -54,6 +69,13 @@ public final class MojangService {
|
||||
log.info("Found UUID for username {}: {}", query, uuid);
|
||||
}
|
||||
|
||||
// Check the cache for the player
|
||||
Optional<CachedPlayer> cached = playerCacheRepository.findById(uuid);
|
||||
if (cached.isPresent()) { // Respond with the cache if present
|
||||
log.info("Found player in cache: {}", uuid);
|
||||
return cached.get();
|
||||
}
|
||||
|
||||
// Send a request to Mojang requesting
|
||||
// the player profile by their UUID
|
||||
try {
|
||||
@ -61,9 +83,19 @@ public final class MojangService {
|
||||
MojangProfileToken token = JsonWebRequest.makeRequest(
|
||||
UUID_TO_PROFILE.formatted(uuid), HttpMethod.GET
|
||||
).execute(MojangProfileToken.class);
|
||||
ProfileAction[] profileActions = token.getProfileActions();
|
||||
|
||||
// Return our player model representing the requested player
|
||||
return new Player(uuid, token.getName(), token.getProfileActions());
|
||||
// Build our player model, cache it, and then return it
|
||||
CachedPlayer player = new CachedPlayer(
|
||||
uuid, token.getName(),
|
||||
profileActions.length == 0 ? null : profileActions,
|
||||
System.currentTimeMillis()
|
||||
);
|
||||
playerCacheRepository.save(player);
|
||||
log.info("Cached player: {}", uuid);
|
||||
|
||||
player.setCached(-1L); // Set to -1 to indicate it's not cached in the response
|
||||
return player;
|
||||
} catch (JsonWebException ex) {
|
||||
// No profile found, return null
|
||||
if (ex.getStatusCode() == 400) {
|
||||
|
Loading…
Reference in New Issue
Block a user