Я работаю над микросервисом поставщика OTP (одноразовых паролей) и хочу кэшировать OTP в Couchbase и получать их по заданному ключу. Я могу добиться этого, используя следующие фрагменты кода. Но я не могу реализовать одну последнюю функцию: OTP должен быть кратковременным и автоматически удаляться из кеша через определенный настраиваемый интервал.
Я использую следующие зависимости -
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-couchbase</artifactId>
</dependency>
и я настроил CacheConfig следующим образом:
@Configuration
public class CacheConfig {
@Value("${couchbase.url}")
private String url;
@Value("${couchbase.username}")
private String username;
@Value("${couchbase.password}")
private String password;
@Value("${couchbase.bucket}")
private String bucket;
public static final String CACHE_NAME = "otp-cache";
@Bean(destroyMethod = "disconnect")
public Cluster cluster() {
return Cluster.connect(url, username, password);
}
@Bean
public CacheManager cacheManager() {
return CouchbaseCacheManager.create(new SimpleCouchbaseClientFactory(cluster(), bucket, null));
}
}
наконец, в моем классе обслуживания, где я генерирую OTP, я использую аннотацию @Cacheable следующим образом:
@Cacheable(cacheNames = CacheConfig.CACHE_NAME, key = "'provider_'+#key")
@Override
public Integer generateOTP(String key) {
StringBuilder generatedOTP = new StringBuilder();
SecureRandom secureRandom = new SecureRandom();
LOGGER.info("generating OTP for provider(not from cache) - "+key);
try {
secureRandom = SecureRandom.getInstance(secureRandom.getAlgorithm());
for (int i = 0; i < LENGTH_OF_OTP; i++) {
generatedOTP.append(secureRandom.nextInt(9));
}
} catch (NoSuchAlgorithmException e) {
LOGGER.error("exception occurred in generating OTP");
}
return Integer.parseInt(generatedOTP.toString());
}
Я встречал некоторые решения, которые предлагают использовать аннотацию @CacheEvict с методом @Scheduled, но при этом будет очищен весь кеш, а не только записи с истекшим сроком действия. Я также попытался обернуть ответ метода @Cacheable в сериализуемый объект и использовал аннотацию @Document(expiryExpression = и @Expiry, но это тоже не работает, кроме того, я получаю исключение приведения класса, когда объект извлекается из кеш и Spring пытаются десериализовать его в класс pojo.
Я также вижу это свойство, используя автоматическое предложение моей IDE в файле свойств приложения Spring.cache.couchbase.expiration=, но это тоже не работает.
Как установить срок действия для каждого OTP, кэшированного в сегменте Couchbase?




Из некоторых ссылок получено, что Spring @Cachebale не поддерживает срок действия (https://stackoverflow.com/a/29191001/11259999), поэтому полностью удален механизм кэширования Spring (удалена зависимость Spring-boot-starter-cache сам) и заменил его явными вызовами Couchbase для чтения/удаления объектов из кеша.
вот изменения в реализации
@Setter
@Getter
@AllArgsConstructor
public class OTP implements Serializable {
String providerId;
int otp;
}
@Autowired
CacheConfig cacheConfig;
public Integer getOrGenerateOTP(String key) {
try {
Cluster cluster = cacheConfig.cluster();
if (cluster.bucket(cacheConfig.getBucket()).defaultCollection().exists(key).exists()) {
GetResult getResult = cluster.bucket(cacheConfig.getBucket()).defaultCollection().get(key);
if (getResult.contentAsObject().containsKey("otp"))
return Integer.parseInt("" + getResult.contentAsObject().get("otp"));
} else {
StringBuilder generatedOTP = new StringBuilder();
SecureRandom secureRandom = new SecureRandom();
LOGGER.info("generating OTP for provider(not from cache) - " + key);
try {
secureRandom = SecureRandom.getInstance(secureRandom.getAlgorithm());
for (int i = 0; i < LENGTH_OF_OTP; i++) {
generatedOTP.append(secureRandom.nextInt(9));
}
} catch (NoSuchAlgorithmException ex) {
LOGGER.error("exception occurred in generating OTP");
}
Temporal startTm = Instant.now();
Temporal endTm = Instant.now().plus(Period.ofDays(2));
Duration d= Duration.ofSeconds(10); // can be used in place of Duration.between(startTm, endTm)
cluster.bucket(cacheConfig.getBucket())
.defaultCollection()
.upsert(key, new OTP(key, Integer.parseInt(generatedOTP.toString())),
UpsertOptions.upsertOptions().expiry(d));
return Integer.parseInt(generatedOTP.toString());
}
} catch (Exception e) {
LOGGER.error(e.getStackTrace());
}
return null;
}
public void clearOtp(String key) {
try {
Cluster cluster = cacheConfig.cluster();
GetResult getResult = cluster.bucket(cacheConfig.getBucket()).defaultCollection().get(key);
cluster.bucket(cacheConfig.getBucket()).defaultCollection().remove(key);
} catch (Exception e) {
LOGGER.error(e.getStackTrace());
}
}
Это решение работает и служит моей цели, но если у кого-то есть обходной путь, который работает вместе с механизмом кэширования Spring, добавьте также сюда ответ. Кроме того, я не уверен, насколько производительность этих двух подходов соотносится друг с другом, поэтому любой ответ, разъясняющий это, также приветствуется.
Для продолжительности жизни 60 минут используйте
CouchbaseCacheConfiguration.defaultCacheConfig().entryExpiry(Duration.ofMinutes(60))
https://docs.spring.io/spring-data/couchbase/reference/couchbase/caching.html
вот обновленный код компонента CacheManager, который работает с моим решением: ``` @Bean public CacheManager cacheManager() { CouchbaseCacheManager.CouchbaseCacheManagerBuilder builder = CouchbaseCacheManager.CouchbaseCacheManagerBuilder .fromConnectionFactory(new SimpleCouchbaseClientFactory(cluster(), Bucket, null)); builder.withCacheConfiguration(CACHE_NAME, CouchbaseCacheConfiguration.defaultCacheConfig().entryExpiry(Duration.ofSeconds(expiryInSec))); вернуть builder.build(); } ```
принимая этот ответ, поскольку он больше соответствует моему первоначальному вопросу. сохраняю и мой ответ, так как он также решает ту же проблему, хотя и с другим подходом