elunez/eladmin · error · BadRequestException
服务异常,请联系网站负责人
Error message
服务异常,请联系网站负责人
What it means
Thrown by VerifyServiceImpl.sendEmail when RedisUtils.set() returns false while trying to store a newly generated 6-digit email verification code under key `key + email`. The code was already generated from the email.ftl template engine, but the cache write failed, so the whole send is aborted with a generic 'service exception, contact the site owner' message. It is a BadRequestException (HTTP 400 surfaced via the global exception handler), masking an infrastructure failure in Redis.
Source
Thrown at eladmin-system/src/main/java/me/zhengjie/modules/system/service/impl/VerifyServiceImpl.java:60
@Value("${code.expiration}")
private Long expiration;
private final RedisUtils redisUtils;
@Override
@Transactional(rollbackFor = Exception.class)
public EmailVo sendEmail(String email, String key) {
EmailVo emailVo;
String content;
String redisKey = key + email;
// 如果不存在有效的验证码,就创建一个新的
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("email.ftl");
String oldCode = redisUtils.get(redisKey, String.class);
if(oldCode == null){
String code = RandomUtil.randomNumbers (6);
// 存入缓存
if(!redisUtils.set(redisKey, code, expiration)){
throw new BadRequestException("服务异常,请联系网站负责人");
}
content = template.render(Dict.create().set("code",code));
// 存在就再次发送原来的验证码
} else {
content = template.render(Dict.create().set("code",oldCode));
}
emailVo = new EmailVo(Collections.singletonList(email),"ELADMIN后台管理系统",content);
return emailVo;
}
@Override
public void validated(String key, String code) {
String value = redisUtils.get(key, String.class);
if(!code.equals(value)){
throw new BadRequestException("无效验证码");
} else {
redisUtils.del(key);
}View on GitHub (pinned to 55fbf70595)
Solutions
- Check Redis connectivity: verify spring.redis host/port/password in the running profile and test with redis-cli ping from the app host.
- Inspect Redis health: run INFO memory / INFO stats to check for maxmemory errors (used_memory > maxmemory, evicted_keys, rejected_connections).
- Look at the application log for the underlying Lettuce/Jedis exception thrown inside RedisUtils.set — the BadRequestException hides it.
- If Redis must be optional in some environment, wrap the sendEmail flow in a health check (e.g. redisUtils.get on a ping key) and return a specific 503-style error instead of this generic message.
- Improve the message or map the failure to a dedicated handler so operators can distinguish 'cache down' from other 400s.
Example fix
// before
if(!redisUtils.set(redisKey, code, expiration)){
throw new BadRequestException("服务异常,请联系网站负责人");
}
// after: surface the infrastructure cause for logs while keeping a client-safe message
if(!redisUtils.set(redisKey, code, expiration)){
log.error("Failed to write verify code to Redis, key={}", redisKey);
throw new BadRequestException("验证码服务暂不可用,请稍后再试");
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: confirm the cache backend answers before offering code sending
boolean cacheOk = false;
try {
cacheOk = redisUtils.get("ping", String.class) != null || true; // any round-trip proves reachability
} catch (Exception ignore) { cacheOk = false; }
if (!cacheOk) {
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body("验证码服务不可用");
} Try / catch
try { verifyService.sendEmail(email, key); } catch (BadRequestException e) { if ("服务异常,请联系网站负责人".equals(e.getMessage())) { /* infra issue: alert ops, do NOT retry immediately */ } else { throw e; } } Prevention
- Monitor Redis availability (health indicator + alerts) since the verify-code feature hard-depends on it.
- Include Redis in docker-compose/healthchecks for local dev so the dependency is always up before tests.
- Alert on the exact message string in APM so cache-write failures are not mistaken for user errors.
When it happens
Trigger: POST /api/code/sendEmail (or the equivalent verifyCode controller) for an email that has no unexpired code in Redis; redisUtils.set(redisKey, code, expiration) returns false — e.g. Redis is down, connection refused, maxmemory reached, or the setex fails. Any first-time code request while Redis is unhealthy produces this.
Common situations: Redis not started or wrong host/port/password in application.yml (spring.redis.*) in dev; Redis evicting keys under memory pressure so set fails; network partition between the app and Redis in containerized deployments; expiration misconfigured to an illegal value.
Related errors
AI-assisted analysis of elunez/eladmin@55fbf70595 (2026-08-14).
Data as JSON: /api/errors/364c606403ccad0a.
Report an issue: GitHub.