feat: add configurations and utility classes

This commit is contained in:
2026-04-03 14:53:59 +08:00
parent 8053bbb6b6
commit 2616e70062
10 changed files with 311 additions and 3 deletions
-3
View File
@@ -44,7 +44,6 @@ dependencies {
implementation(libs.spring.boot.starter.validation) implementation(libs.spring.boot.starter.validation)
implementation(libs.spring.boot.starter.redis) implementation(libs.spring.boot.starter.redis)
implementation(libs.spring.boot.starter.cache) implementation(libs.spring.boot.starter.cache)
implementation(libs.spring.boot.starter.security)
implementation(libs.spring.boot.starter.jpa) implementation(libs.spring.boot.starter.jpa)
implementation(libs.mybatis.starter.core) implementation(libs.mybatis.starter.core)
implementation(libs.flyway.core) implementation(libs.flyway.core)
@@ -52,9 +51,7 @@ dependencies {
implementation(libs.jackson.jsr310) implementation(libs.jackson.jsr310)
testImplementation(libs.spring.boot.starter.test) testImplementation(libs.spring.boot.starter.test)
testImplementation(libs.reactor.test) testImplementation(libs.reactor.test)
testImplementation(libs.spring.security.test)
testImplementation(libs.mybatis.starter.test) testImplementation(libs.mybatis.starter.test)
// runtimeOnly(libs.postgres.driver)
runtimeOnly(libs.mysql.driver) runtimeOnly(libs.mysql.driver)
testRuntimeOnly(libs.h2.database) testRuntimeOnly(libs.h2.database)
testRuntimeOnly(libs.junit.launcher) testRuntimeOnly(libs.junit.launcher)
@@ -0,0 +1,102 @@
package com.onixbyte.deltaforceguide.config;
import com.onixbyte.deltaforceguide.shared.JacksonRedisSerialiser;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
/**
* Configuration class for Redis-based caching components.
* <p>
* This configuration class provides beans for Redis cache management and template operations
* within the Helix application. It configures custom serialisation strategies using
* {@link GenericJackson2JsonRedisSerializer} for values and string serialisation for keys,
* ensuring optimal performance and compatibility with JSON-based data structures.
* <p>
* The configuration includes:
* <ul>
* <li>Custom {@link RedisCacheManager} with JSON serialisation support</li>
* <li>Configured {@link RedisTemplate} for direct Redis operations</li>
* </ul>
*
* @author zihluwang
* @see RedisCacheManager
* @see RedisTemplate
* @see GenericJackson2JsonRedisSerializer
* @since 1.0.0
*/
@Configuration
@EnableCaching
public class CacheConfig {
/**
* Creates a custom Redis cache manager with JSON serialisation support.
* <p>
* This method configures a {@link RedisCacheManager} that uses string serialisation for cache
* keys and {@link GenericJackson2JsonRedisSerializer} for cache values. This setup ensures that
* complex objects can be stored and retrieved from Redis cache whilst maintaining readability
* and compatibility with JSON-based systems.
*
* @param connectionFactory the Redis connection factory used to establish connections
* @return a configured {@link RedisCacheManager} with custom serialisation settings
* @see RedisCacheManager
* @see GenericJackson2JsonRedisSerializer
* @see RedisSerializationContext
*/
@Bean
public RedisCacheManager cacheManager(
RedisConnectionFactory connectionFactory
) {
var _keySerializer = RedisSerializer.string();
var cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(_keySerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(JacksonRedisSerialiser.INSTANCE))
.entryTtl(Duration.ofMinutes(90L));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(connectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
/**
* Creates a Redis template for direct Redis operations with custom serialisation.
* <p>
* This method configures a {@link RedisTemplate} that uses string serialisation for keys
* and {@link GenericJackson2JsonRedisSerializer} for values. This template provides low-level
* access to Redis operations whilst ensuring consistent serialisation strategies across
* the application.
* <p>
* The template is fully configured and ready for use after bean creation.
*
* @param connectionFactory the Redis connection factory used to establish connections
* @return a fully configured {@link RedisTemplate} for Redis operations
* @see RedisTemplate
* @see GenericJackson2JsonRedisSerializer
* @see RedisSerializer
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory
) {
var redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(JacksonRedisSerialiser.INSTANCE);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
@@ -0,0 +1,43 @@
package com.onixbyte.deltaforceguide.config;
import com.onixbyte.deltaforceguide.properties.CorsProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Optional;
import java.util.stream.Stream;
@Configuration
@EnableConfigurationProperties({CorsProperties.class})
public class CorsConfig implements WebMvcConfigurer {
private final CorsProperties properties;
public CorsConfig(CorsProperties properties) {
this.properties = properties;
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins(properties.allowedOrigins())
.allowedHeaders(properties.allowedHeaders())
.allowedMethods(toHttpMethodNames(properties.allowedMethods()))
.allowCredentials(properties.allowCredentials())
.maxAge(properties.maxAge().toSeconds())
.exposedHeaders(properties.exposedHeaders());
}
private static String[] toHttpMethodNames(HttpMethod[] methods) {
return Optional.ofNullable(methods)
.stream()
.flatMap(Stream::of)
.map(HttpMethod::name)
.toList()
.toArray(String[]::new);
}
}
@@ -0,0 +1,20 @@
package com.onixbyte.deltaforceguide.config;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.onixbyte.deltaforceguide.shared.JacksonModules;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomiser() {
return (builder) -> {
builder.modules(JacksonModules.DATE_TIME_MODULE);
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
};
}
}
@@ -0,0 +1,10 @@
package com.onixbyte.deltaforceguide.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan(basePackages = {"com.onixbyte.deltaforceguide.mapper"})
public class MyBatisConfig {
}
@@ -0,0 +1,10 @@
package com.onixbyte.deltaforceguide.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration
@EnableJpaRepositories(basePackages = {"com.onixbyte.deltaforceguide.repository"})
public class SpringDataConfig {
}
@@ -0,0 +1,24 @@
package com.onixbyte.deltaforceguide.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.http.HttpMethod;
import java.time.Duration;
@ConfigurationProperties(prefix = "app.cors")
public record CorsProperties(
@DefaultValue({"Content-Type", "Authorization"})
String[] allowedHeaders,
@DefaultValue({"GET", "POST", "PUT", "PATCH", "DELETE"})
HttpMethod[] allowedMethods,
String[] allowedOrigins,
boolean allowCredentials,
boolean allowPrivateNetwork,
@DefaultValue("PT2H")
Duration maxAge,
@DefaultValue({"Content-Type", "Authorization"})
String[] exposedHeaders
) {
}
@@ -0,0 +1,46 @@
package com.onixbyte.deltaforceguide.shared;
import java.time.format.DateTimeFormatter;
/**
* Utility class providing predefined {@link DateTimeFormatter} instances for common date and
* time patterns. These formatters can be used to parse and format {@code java.time} objects
* consistently throughout an application.
*
* @author zihluwang
*/
public class DateTimeFormatters {
/**
* A {@link DateTimeFormatter} for formatting and parsing full date and time with seconds, using
* the pattern "yyyy-MM-dd HH:mm:ss".
* <p>
* Example: "{@code 2023-10-27 15:30:45}"
*/
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* A {@link DateTimeFormatter} for formatting and parsing dates only, using the
* pattern "yyyy-MM-dd".
* <p>
* Example: "{@code 2023-10-27}"
*/
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* A {@link DateTimeFormatter} for formatting and parsing times only, using the
* pattern "HH:mm:ss".
* <p>
* Example: "{@code 15:30:45}"
*/
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
/**
* A {@link DateTimeFormatter} for formatting and parsing year and month only, using the
* pattern "yyyy-MM".
* <p>
* Example: "{@code 2023-10}"
*/
public static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
}
@@ -0,0 +1,34 @@
package com.onixbyte.deltaforceguide.shared;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class JacksonModules {
public static final SimpleModule DATE_TIME_MODULE = initialiseDateTimeModule();
private static SimpleModule initialiseDateTimeModule() {
var javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatters.DATE_TIME_FORMATTER));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatters.DATE_TIME_FORMATTER));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatters.DATE_FORMATTER));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatters.DATE_FORMATTER));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatters.TIME_FORMATTER));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatters.TIME_FORMATTER));
return javaTimeModule;
}
}
@@ -0,0 +1,22 @@
package com.onixbyte.deltaforceguide.shared;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
public class JacksonRedisSerialiser {
public static final GenericJackson2JsonRedisSerializer INSTANCE = initialiseSerializer();
private static GenericJackson2JsonRedisSerializer initialiseSerializer() {
var serializer = new GenericJackson2JsonRedisSerializer();
serializer.configure((configurer) -> {
configurer.registerModule(new JavaTimeModule());
configurer.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
});
return serializer;
}
}