登录失败应抛出自定义LoginException异常,通过静态工厂方法明确区分“账号不存在”“密码错误”等语义,并在Service层主动校验后throw,再由@ControllerAdvice统一处理返回前端标准响应。

登录失败时抛出自定义异常,核心是先定义异常类,再在业务逻辑中按条件主动 throw,而不是等框架自动抛出。关键在于让异常携带明确的错误语义(比如“账号不存在”“密码错误”“账户被禁用”),便于上层统一处理或返回前端友好提示。
定义登录专用的自定义异常类
继承 RuntimeException(非检查异常,调用方无需强制 try-catch),并提供带错误码和提示信息的构造方法:
public class LoginException extends RuntimeException {
private final int errorCode;
public LoginException(int errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
// 可选:提供常用错误码的静态方法,提升可读性
public static LoginException accountNotFound() {
return new LoginException(4001, "账号不存在");
}
public static LoginException passwordMismatch() {
return new LoginException(4002, "密码错误");
}
public static LoginException accountDisabled() {
return new LoginException(4003, "账户已被禁用");
}
public int getErrorCode() {
return errorCode;
}
}
在 Service 层登录逻辑中按规则抛出
不要依赖空指针或数据库异常来“间接”表示失败;而是显式校验、明确判断、主动抛出对应异常:
- 查不到用户 → 抛
LoginException.accountNotFound() - 密码不匹配 → 抛
LoginException.passwordMismatch() - 用户状态为禁用 → 抛
LoginException.accountDisabled() - 验证码错误、登录频繁限制等场景,也可扩展对应静态工厂方法
@Service
public class UserService {
public User login(String username, String rawPassword, String captcha) {
// 1. 校验验证码(略)
// 2. 查询用户
User user = userMapper.findByUsername(username);
if (user == null) {
throw LoginException.accountNotFound(); // 明确语义
}
if (!user.isEnabled()) {
throw LoginException.accountDisabled();
}
if (!passwordEncoder.matches(rawPassword, user.getPassword())) {
throw LoginException.passwordMismatch();
}
return user; // 登录成功
}
}
全局异常处理器统一响应前端
用 @ControllerAdvice 捕获 LoginException,转成标准 JSON 响应(如 { "code": 4002, "msg": "密码错误" }):
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(LoginException.class)
public ResponseEntity<apiresponse> handleLoginException(LoginException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ApiResponse.fail(e.getErrorCode(), e.getMessage()));
}
}</apiresponse>
这样前端能根据 errorCode 做差异化提示(例如 4002 触发“密码输错了?”气泡,4001 引导“去注册?”)。
避免常见误区
- 不要把所有登录失败都 throw 同一个异常(如泛化的
BusinessException),丢失区分度 - 不要在 Controller 层做登录校验并 throw —— 业务规则应在 Service 层封装,保持分层清晰
- 异常消息不要直接返回敏感信息(如 “密码错误,但邮箱正确”),生产环境需脱敏
- 日志中可记录完整上下文(用户名、IP、时间),但响应体只返回安全提示
不复杂但容易忽略:异常是业务语言的一部分,登录失败不是“程序出错了”,而是“用户操作未满足业务前提”,该用什么异常,取决于你想告诉调用方什么。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










