VerificationCodeService.java
package com.yumu.noveltranslator.domain.service;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* Verification code generation, storage, and verification domain service.
* Extracted from EmailVerificationCodeUtil to separate concerns:
* this handles code lifecycle, while EmailPort handles email delivery.
*/
@Service
@Slf4j
public class VerificationCodeService {
private Cache<String, String> verificationCodeCache;
private Cache<String, Long> lastSendTimeCache;
@Value("${email.verification.code.validity:1}")
private long validity;
@Value("${email.verification.code.length:6}")
private int codeLength;
private static final Random RANDOM = new Random();
@PostConstruct
public void init() {
verificationCodeCache = Caffeine.newBuilder()
.expireAfterWrite(validity, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
lastSendTimeCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
}
/**
* Generate a random numeric code and store it in cache.
* @return the generated code
*/
public String generateAndStore(String email) {
String code = generateCode();
verificationCodeCache.put(email + ":" + code, code);
lastSendTimeCache.put(email, System.currentTimeMillis());
log.info("验证码已生成: {}", email);
return code;
}
/**
* Verify the code for the given email.
* @return true if valid and not expired
*/
public boolean verifyCode(String email, String code) {
String cachedCode = verificationCodeCache.getIfPresent(email + ":" + code);
if (cachedCode == null) {
log.warn("验证码已过期或不存在,邮箱: {}", email);
return false;
}
verificationCodeCache.invalidate(email + ":" + code);
log.info("邮箱验证成功: {}", email);
return true;
}
/**
* Check if a code was sent recently (within 60 seconds).
* @return true if rate-limited
*/
public boolean isRateLimited(String email) {
Long lastSendTime = lastSendTimeCache.getIfPresent(email);
if (lastSendTime != null) {
long elapsed = System.currentTimeMillis() - lastSendTime;
return elapsed < 60000;
}
return false;
}
public Long getLastSendTime(String email) {
return lastSendTimeCache.getIfPresent(email);
}
private String generateCode() {
StringBuilder code = new StringBuilder();
for (int i = 0; i < codeLength; i++) {
code.append(RANDOM.nextInt(10));
}
return code.toString();
}
}