Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package life.mosu.mosuserver.application.exam.cache;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.infra.persistence.redis.operator.VoidCacheAtomicOperator;
Expand All @@ -15,12 +17,17 @@ public class AtomicExamQuotaDecrementOperator implements VoidCacheAtomicOperator
private final RedisTemplate<String, Long> redisTemplate;
private final DefaultRedisScript<Long> decrementScript;


public AtomicExamQuotaDecrementOperator(
RedisTemplate<String, Long> redisTemplate,
@Qualifier("decrementExamQuotaScript") DefaultRedisScript<Long> decrementScript

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Qualifier("examLuaScripts")
Map<String, DefaultRedisScript<Long>> examLuaScripts
) {
this.redisTemplate = redisTemplate;
this.decrementScript = decrementScript;
this.decrementScript = Objects.requireNonNull(examLuaScripts.get("decrementQuota"),
"Redis script 'decrementQuota' not found");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
package life.mosu.mosuserver.application.exam.cache;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.infra.persistence.redis.operator.VoidCacheAtomicOperator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class AtomicExamQuotaIncrementOperator implements VoidCacheAtomicOperator<String, Long> {

private final RedisTemplate<String, Long> redisTemplate;
private final DefaultRedisScript<Long> decrementScript;
private final DefaultRedisScript<Long> incrementScript;

public AtomicExamQuotaIncrementOperator(
RedisTemplate<String, Long> redisTemplate,
@Qualifier("incrementExamQuotaScript") DefaultRedisScript<Long> decrementScript

@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Qualifier("examLuaScripts")
Map<String, DefaultRedisScript<Long>> examLuaScripts
) {
this.redisTemplate = redisTemplate;
this.decrementScript = decrementScript;
this.incrementScript = Objects.requireNonNull(examLuaScripts.get("incrementQuota"),
"Redis script 'incrementQuota' not found");
}

@Override
Expand All @@ -36,7 +44,7 @@ public String getActionName() {
@Override
public void execute(String key) {
try {
Long result = redisTemplate.execute(decrementScript, List.of(
Long result = redisTemplate.execute(incrementScript, List.of(
ExamQuotaPrefix.CURRENT_APPLICATIONS.with(key),
ExamQuotaPrefix.MAX_CAPACITY.with(key)
));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import life.mosu.mosuserver.infra.persistence.redis.operator.VoidCacheAtomicOperator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -29,7 +30,9 @@ public ExamQuotaCacheManager(
CacheWriter<String, Long> cacheWriter,
CacheReader<String, Long> cacheReader,

@Qualifier("examCacheAtomicOperatorMap")
@Lazy
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
@Qualifier("examQuotaCacheAtomicOperatorMap")
Map<String, ? extends CacheAtomicOperator<String, Long>> cacheAtomicOperatorMap,
ExamJpaRepository examJpaRepository
) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package life.mosu.mosuserver.infra.config;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import life.mosu.mosuserver.infra.persistence.redis.operator.CacheAtomicOperator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class AtomicOperatorAutoRegistrar implements SmartInitializingSingleton {

private final ConfigurableListableBeanFactory beanFactory;

public AtomicOperatorAutoRegistrar(ConfigurableListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}

@Override
public void afterSingletonsInstantiated() {
Map<String, CacheAtomicOperator> allOperators = beanFactory.getBeansOfType(
CacheAtomicOperator.class);

Map<String, List<CacheAtomicOperator>> grouped = allOperators.values().stream()
.collect(Collectors.groupingBy(CacheAtomicOperator::getName));

DefaultListableBeanFactory registry = (DefaultListableBeanFactory) beanFactory;
for (Map.Entry<String, List<CacheAtomicOperator>> entry : grouped.entrySet()) {
String domain = entry.getKey();
List<CacheAtomicOperator> operators = entry.getValue();

Map<String, CacheAtomicOperator> mapValue = operators.stream()
.collect(Collectors.toMap(CacheAtomicOperator::getActionName,
Function.identity()));

String beanName = domain + "CacheAtomicOperatorMap";
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(Map.class);
beanDefinition.setInstanceSupplier(() -> mapValue);

registry.registerBeanDefinition(beanName, beanDefinition);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package life.mosu.mosuserver.infra.persistence.redis.support;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.redis.core.script.DefaultRedisScript;

/**
* FQCN
*/
@Slf4j
public class LuaScriptsFunctionalRegistrar implements
ApplicationContextInitializer<GenericApplicationContext> {

private static final String SCRIPT_BASE_PATH = "classpath:scripts/";
private static final String SCRIPT_PATH_PREFIX = "/scripts/";

@Override
public void initialize(GenericApplicationContext context) {
try {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(SCRIPT_BASE_PATH + "**/*.lua");

Map<String, Map<String, DefaultRedisScript<Long>>> domainScriptsMap = new HashMap<>();

for (Resource resource : resources) {
String path = resource.getURL().getPath();
int idx = path.lastIndexOf(SCRIPT_PATH_PREFIX);
if (idx < 0) {
continue;
}

String relativePath = path.substring(idx + SCRIPT_PATH_PREFIX.length());
String[] parts = relativePath.split("/");
if (parts.length < 2) {
continue;
}

String domain = parts[0];
String filename = parts[1];
String scriptName = toCamelCase(filename.replace(".lua", ""));

DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setResultType(Long.class);

try (InputStream is = resource.getInputStream()) {
String lua = new String(is.readAllBytes(), StandardCharsets.UTF_8);
script.setScriptText(lua);
}

domainScriptsMap
.computeIfAbsent(domain, k -> new HashMap<>())
.put(scriptName, script);
}

for (Map.Entry<String, Map<String, DefaultRedisScript<Long>>> entry : domainScriptsMap.entrySet()) {
String beanName = entry.getKey() + "LuaScripts";
log.info("Registering Lua scripts for domain: {}", beanName);
Map<String, DefaultRedisScript<Long>> scripts = entry.getValue();

String keys = String.join(", ", scripts.keySet());
log.info("Lua script keys: [{}]", keys);

context.registerBean(beanName, Map.class, () -> scripts);
}
} catch (Exception e) {
throw new RuntimeException("Failed to load Lua scripts", e);
}
}

private String toCamelCase(String snakeCase) {
StringBuilder result = new StringBuilder();
boolean toUpper = false;
for (char c : snakeCase.toCharArray()) {
if (c == '_') {
toUpper = true;
} else {
result.append(toUpper ? Character.toUpperCase(c) : c);
toUpper = false;
}
}
return result.toString();
}
}
Empty file.
2 changes: 2 additions & 0 deletions src/main/resources/META-INF/spring.factories
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.context.ApplicationContextInitializer=\
life.mosu.mosuserver.infra.persistence.redis.support.LuaScriptsFunctionalRegistrar
Empty file.