Claude-skill-registry error-handler
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/error-handler" ~/.claude/skills/majiayu000-claude-skill-registry-error-handler && rm -rf "$T"
manifest:
skills/data/error-handler/SKILL.mdsource content
异常处理规范
核心规范
规范1:业务异常使用ServiceException抛出
详细说明: 在Service层处理业务逻辑时,遇到以下情况必须抛出
ServiceException:
- 参数校验失败(如格式错误、必填项缺失)
- 业务规则违反(如状态冲突、权限不足)
- 数据唯一性校验失败(如用户名已存在)
- 业务流程中断(如库存不足、账户余额不足)
关键要点:
- ✅ 必须抛出
,切勿直接返回封装对象R或仅打印日志ServiceException - ✅ 配合
注解可自动触发事务回滚@Transactional - ✅ 异常信息应清晰明确,便于前端展示给用户
- ✅ 可指定错误码:
throw new ServiceException("错误信息", 错误码)
@Service public class SysUserServiceImpl implements ISysUserService { @Autowired private SysUserMapper userMapper; /** * 校验用户名唯一性 * 示例:数据唯一性校验场景 */ @Override public void checkUserNameUnique(String userName) { int count = userMapper.checkUserNameUnique(userName); if (count > 0) { // 抛出业务异常,前端接收到统一格式的错误提示 throw new ServiceException("新增用户'" + userName + "'失败,登录账号已存在"); } } /** * 新增用户 * 示例:带事务的业务操作,异常自动回滚 */ @Override @Transactional(rollbackFor = Exception.class) public int insertUser(SysUser user) { // 业务校验失败抛出异常,事务自动回滚 if (StringUtils.isNotEmpty(user.getPhonenumber()) && !UserConstants.isPhone(user.getPhonenumber())) { throw new ServiceException("手机号格式不正确"); } // 检查用户名唯一性 checkUserNameUnique(user.getUserName()); // 执行插入操作 int rows = userMapper.insertUser(user); if (rows == 0) { throw new ServiceException("新增用户失败"); } return rows; } /** * 带错误码的异常抛出 * 示例:指定特定错误码场景 */ @Override public void updateUserStatus(Long userId, String status) { SysUser user = userMapper.selectUserById(userId); if (Objects.isNull(user)) { // 指定错误码 404 throw new ServiceException("用户不存在", HttpStatus.NOT_FOUND); } if ("1".equals(user.getDelFlag())) { throw new ServiceException("用户已删除,无法修改状态", HttpStatus.BAD_REQUEST); } userMapper.updateUserStatus(userId, status); } }
规范2:全局异常处理器统一捕获
详细说明: 使用
@RestControllerAdvice注解定义全局异常处理类,集中处理所有异常情况:
- 捕获
业务异常ServiceException - 捕获
参数校验异常MethodArgumentNotValidException - 捕获
运行时异常RuntimeException - 捕获数据库异常、权限异常等系统异常
关键要点:
- ✅ 必须记录完整的错误日志(包括请求URI、异常信息、堆栈跟踪)
- ✅ 必须返回标准格式
对象,确保前端获得一致的错误响应结构R - ✅ 敏感信息(如SQL错误、系统路径)不得暴露给前端
- ✅ 区分不同异常类型,返回合适的HTTP状态码和业务错误码
@RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); /** * 业务异常 * 处理Service层主动抛出的业务异常 */ @ExceptionHandler(ServiceException.class) public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) { log.error("业务异常 => 请求地址: {}, 异常信息: {}", request.getRequestURI(), e.getMessage()); Integer code = e.getCode(); return StringUtils.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage()); } /** * 请求参数校验异常 * 处理@Valid或@Validated注解校验失败的情况 */ @ExceptionHandler(MethodArgumentNotValidException.class) public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { log.error("参数校验异常", e); String message = e.getBindingResult().getFieldError().getDefaultMessage(); return R.fail(message); } /** * 请求方式不支持异常 * 如:前端用POST请求了GET接口 */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { log.error("请求方式不支持 => 请求地址: {}, 请求方式: {}", request.getRequestURI(), e.getMethod()); return R.fail("不支持'" + e.getMethod() + "'请求方式"); } /** * 数据库异常 * 捕获SQL异常,避免暴露数据库信息 */ @ExceptionHandler(SQLException.class) public R<Void> handleSQLException(SQLException e, HttpServletRequest request) { log.error("数据库异常 => 请求地址: {}, SQL状态码: {}, 错误信息: {}", request.getRequestURI(), e.getSQLState(), e.getMessage(), e); return R.fail("数据库操作异常,请联系管理员"); } /** * 空指针异常 * 通常是代码bug,需要开发人员修复 */ @ExceptionHandler(NullPointerException.class) public R<Void> handleNullPointerException(NullPointerException e, HttpServletRequest request) { log.error("空指针异常 => 请求地址: {}", request.getRequestURI(), e); return R.fail("系统异常,请联系管理员"); } /** * 运行时异常 * 兜底处理,捕获所有未被明确处理的RuntimeException */ @ExceptionHandler(RuntimeException.class) public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) { log.error("运行时异常 => 请求地址: {}", request.getRequestURI(), e); return R.fail("系统运行异常,请稍后重试"); } /** * 系统异常 * 最终兜底,捕获所有异常 */ @ExceptionHandler(Exception.class) public R<Void> handleException(Exception e, HttpServletRequest request) { log.error("系统异常 => 请求地址: {}", request.getRequestURI(), e); return R.fail("系统异常,请联系管理员"); } }
规范3:自定义异常类设计
详细说明: 除了框架提供的
ServiceException,根据业务需要可自定义特定异常类:
- 继承
以支持事务回滚RuntimeException - 包含错误码、错误信息、详细描述等属性
- 命名应清晰表达异常类型(如
、UserNotFoundException
)InsufficientBalanceException
示例代码:
/** * 自定义业务异常基类 */ public class BaseException extends RuntimeException { private static final long serialVersionUID = 1L; /** 错误码 */ private Integer code; /** 错误详细信息(不展示给前端) */ private String detailMessage; public BaseException(String message) { super(message); } public BaseException(String message, Integer code) { super(message); this.code = code; } public BaseException(String message, Integer code, String detailMessage) { super(message); this.code = code; this.detailMessage = detailMessage; } public Integer getCode() { return code; } public String getDetailMessage() { return detailMessage; } } /** * 具体业务异常示例 */ public class UserNotFoundException extends BaseException { public UserNotFoundException(Long userId) { super("用户不存在", HttpStatus.NOT_FOUND, "userId: " + userId); } }
规范4:Controller层异常处理
详细说明: Controller层应专注于请求参数接收和响应返回,不应处理业务异常:
- ✅ 允许:使用
或@Valid
进行参数校验@Validated - ✅ 允许:直接调用Service方法,让异常向上抛出
- ❌ 禁止:使用try-catch捕获Service层抛出的异常
- ❌ 禁止:在Controller中编写业务逻辑
正确示例:
@RestController @RequestMapping("/system/user") public class SysUserController { @Autowired private ISysUserService userService; /** * 新增用户 * 正确:直接调用Service,不捕获异常 */ @PostMapping public R<Void> add(@Validated @RequestBody SysUser user) { // Service层抛出的异常会被全局异常处理器捕获 userService.insertUser(user); return R.ok(); } /** * 修改用户 * 正确:参数校验由框架自动处理 */ @PutMapping public R<Void> edit(@Validated @RequestBody SysUser user) { userService.updateUser(user); return R.ok(); } }
错误示例(禁止):
@RestController @RequestMapping("/system/user") public class SysUserController { /** * 错误示例:Controller层不应捕获业务异常 */ @PostMapping public R<Void> add(@RequestBody SysUser user) { try { userService.insertUser(user); return R.ok(); } catch (ServiceException e) { // ❌ 禁止:不应在Controller中捕获异常 log.error("新增用户失败", e); return R.fail(e.getMessage()); } } }
禁止事项
异常抛出禁止项
-
❌ 禁止在Service层直接返回R对象处理异常
- 错误:
return R.fail("用户名已存在"); - 正确:
throw new ServiceException("用户名已存在");
- 错误:
-
❌ 禁止吞没异常
- 错误:捕获异常后只打印日志,不重新抛出或处理
try { // 业务操作 } catch (Exception e) { log.error("操作失败", e); // 只记录日志,不处理 // 异常被吞没,事务无法回滚 } -
❌ 禁止在Controller层使用try-catch捕获业务异常
- 错误:在Controller中捕获ServiceException并返回R对象
- 正确:让异常向上抛出,由全局异常处理器统一处理
-
❌ 禁止捕获Exception或Throwable后不抛出
- 错误:
catch (Exception e) { log.error("error", e); } - 正确:
catch (Exception e) { log.error("error", e); throw e; }
- 错误:
异常处理禁止项
-
❌ 禁止将数据库异常信息直接返回给前端
- 错误:返回"Duplicate entry 'admin' for key 'user.username'"
- 正确:返回"用户名已存在"
-
❌ 禁止暴露系统内部信息
- 错误:返回堆栈跟踪、文件路径、SQL语句
- 正确:返回用户友好的提示信息
-
❌ 禁止使用e.printStackTrace()
- 错误:
e.printStackTrace(); - 正确:
log.error("错误信息", e);
- 错误:
事务处理禁止项
-
❌ 禁止在@Transactional方法中捕获异常不抛出
- 捕获异常后不重新抛出会导致事务无法回滚
@Transactional public void updateUser(SysUser user) { try { userMapper.updateUser(user); roleMapper.updateUserRole(user.getId()); } catch (Exception e) { log.error("更新失败", e); // ❌ 异常被吞没,事务不会回滚 } } -
❌ 禁止抛出受检异常(Checked Exception)不配置回滚
- 受检异常默认不触发事务回滚
- 必须使用
@Transactional(rollbackFor = Exception.class) - 推荐直接使用RuntimeException或其子类
日志记录禁止项
-
❌ 禁止记录日志时不记录异常堆栈
- 错误:
log.error("操作失败: " + e.getMessage()); - 正确:
log.error("操作失败", e);
- 错误:
-
❌ 禁止在循环中记录相同的异常信息
- 会导致日志爆炸
for (User user : users) { try { processUser(user); } catch (Exception e) { log.error("处理用户失败", e); // ❌ 循环中重复记录 } }
响应格式禁止项
-
❌ 禁止返回非标准R对象格式
- 错误:
return "error message"; - 错误:
return Map.of("error", "message"); - 正确:
return R.fail("error message");
- 错误:
-
❌ 禁止在异常处理中返回null
- 全局异常处理器必须返回有效的R对象
参考代码
核心文件路径
- ServiceException类:
ruoyi-common/src/main/java/com/ruoyi/common/exception/ServiceException.java - 全局异常处理器:
ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java - 统一响应对象R:
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/R.java - HTTP状态码常量:
ruoyi-common/src/main/java/com/ruoyi/common/constant/HttpStatus.java
相关依赖
<!-- Spring Web (包含@RestControllerAdvice等注解) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Validation (参数校验) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
检查清单
基础配置检查
- 是否已配置全局异常处理器(@RestControllerAdvice)
- 是否已定义ServiceException或自定义异常类
- 是否已配置统一响应对象R
- 是否已引入必要的依赖(spring-boot-starter-web、validation)
异常抛出规范检查
- Service层是否使用ServiceException抛出业务异常
- 是否避免在Service层直接返回R对象处理异常
- 异常信息是否清晰明确,便于用户理解
- 是否正确使用错误码(可选)
- 关键业务方法是否添加@Transactional注解
异常处理规范检查
- 全局异常处理器是否捕获了主要异常类型(ServiceException、RuntimeException、Exception等)
- 是否所有异常返回均为统一的R对象格式
- 是否在异常处理中记录了完整的Error Log(包含堆栈信息)
- 是否避免暴露敏感信息(SQL、路径、堆栈)给前端
- 是否针对不同异常类型返回合适的提示信息
事务回滚检查
- 是否确保异常发生时的事务回滚机制
- @Transactional方法中是否避免捕获异常不抛出
- 是否正确配置rollbackFor属性(建议rollbackFor = Exception.class)
- 是否避免使用受检异常(Checked Exception)
Controller层检查
- Controller是否避免使用try-catch捕获业务异常
- 是否使用@Valid或@Validated进行参数校验
- 是否直接调用Service方法,让异常向上抛出
- 是否避免在Controller中编写业务逻辑
日志记录检查
- 是否使用log.error记录异常而非e.printStackTrace()
- 记录日志时是否包含异常堆栈信息(传入Exception对象)
- 日志信息是否包含请求URI、关键参数等上下文信息
- 是否避免在循环中重复记录相同异常
代码质量检查
- 是否避免吞没异常(捕获后不处理也不抛出)
- 是否避免捕获Exception或Throwable后不抛出
- 异常处理逻辑是否简洁清晰,易于维护
- 是否编写了单元测试验证异常处理逻辑
常见问题FAQ
Q1: ServiceException和RuntimeException有什么区别?
A:
ServiceException是若依框架中继承自RuntimeException的自定义业务异常类,相比直接使用RuntimeException:
- 支持自定义错误码
- 语义更明确,表示业务逻辑异常
- 便于全局异常处理器识别和处理
- 建议业务层统一使用ServiceException
Q2: 什么时候需要在Controller层捕获异常?
A: 几乎不需要。以下场景可以例外:
- 需要在异常发生后执行特殊清理操作(如释放资源)
- 需要将异常转换为其他类型后重新抛出
- 需要记录额外的业务上下文信息后重新抛出
- 关键:即使捕获也必须重新抛出,不能吞没异常
Q3: 如何处理异步方法中的异常?
A: 异步方法(@Async)中的异常不会被全局异常处理器捕获,需要:
@Async public CompletableFuture<Void> asyncTask() { return CompletableFuture.runAsync(() -> { try { // 业务逻辑 } catch (Exception e) { log.error("异步任务执行失败", e); // 可以发送消息通知、记录数据库等 } }); }
Q4: 如何区分哪些异常需要回滚事务?
A: Spring事务默认行为:
- 会回滚:RuntimeException及其子类(包括ServiceException)
- 不会回滚:受检异常(Checked Exception,如IOException)
- 建议配置:
对所有异常都回滚@Transactional(rollbackFor = Exception.class)
Q5: 全局异常处理器的执行顺序是什么?
A: 按照异常的继承关系,从子类到父类:
- 先匹配具体异常(如ServiceException)
- 再匹配父类异常(如RuntimeException)
- 最后匹配Exception
- 建议:将Exception的处理方法放在最后作为兜底
Q6: 如何在异常信息中避免SQL注入风险?
A: 构造异常信息时要注意:
// ❌ 错误:直接拼接用户输入 throw new ServiceException("查询失败: " + userInput); // ✅ 正确:使用占位符或参数化 throw new ServiceException("查询失败,请检查输入参数"); log.error("查询失败,用户输入: {}", userInput);
Q7: 如何处理第三方API调用异常?
A: 建议封装为业务异常:
@Service public class ThirdPartyService { public String callExternalApi(String param) { try { return restTemplate.getForObject(url, String.class); } catch (HttpClientErrorException e) { log.error("调用第三方API失败: {}", e.getMessage(), e); throw new ServiceException("外部服务暂时不可用,请稍后重试"); } catch (Exception e) { log.error("调用第三方API异常", e); throw new ServiceException("系统异常,请联系管理员"); } } }
最佳实践建议
1. 异常信息编写原则
- 用户友好:使用通俗易懂的语言,避免技术术语
- 明确具体:指出问题所在,而非模糊的"操作失败"
- 可操作:告诉用户如何解决问题
// ❌ 不好:操作失败 throw new ServiceException("操作失败"); // ✅ 良好:用户名已存在,请使用其他用户名 throw new ServiceException("用户名'" + userName + "'已存在,请使用其他用户名");
2. 错误码设计规范
建议采用分层错误码体系:
public class ErrorCode { // 系统级错误 (1xxx) public static final int SYSTEM_ERROR = 1000; public static final int DB_ERROR = 1001; // 业务级错误 (2xxx) public static final int USER_NOT_FOUND = 2001; public static final int USER_ALREADY_EXISTS = 2002; public static final int INSUFFICIENT_BALANCE = 2003; // 参数错误 (3xxx) public static final int PARAM_INVALID = 3000; public static final int PARAM_MISSING = 3001; }
3. 分层异常处理策略
┌─────────────────────────────────────┐ │ Controller层 │ │ - 只负责接收请求和返回响应 │ │ - 不捕获异常(让异常向上传播) │ └──────────────┬──────────────────────┘ │ 异常向上抛出 ┌──────────────▼──────────────────────┐ │ Service层 │ │ - 处理业务逻辑 │ │ - 抛出ServiceException业务异常 │ │ - 配合@Transactional进行事务管理 │ └──────────────┬──────────────────────┘ │ 异常继续向上抛出 ┌──────────────▼──────────────────────┐ │ GlobalExceptionHandler │ │ - 统一捕获所有异常 │ │ - 记录日志 │ │ - 返回标准R对象 │ └─────────────────────────────────────┘
4. 事务边界最佳实践
@Service public class OrderService { /** * 正确:事务边界清晰,异常自动回滚 */ @Transactional(rollbackFor = Exception.class) public void createOrder(Order order) { // 1. 校验库存 if (!checkStock(order.getProductId(), order.getQuantity())) { throw new ServiceException("库存不足"); } // 2. 创建订单 orderMapper.insert(order); // 3. 扣减库存 productMapper.decreaseStock(order.getProductId(), order.getQuantity()); // 4. 扣减余额 accountMapper.decreaseBalance(order.getUserId(), order.getAmount()); // 任何步骤抛出异常,所有操作都会回滚 } }
5. 日志记录最佳实践
@RestControllerAdvice public class GlobalExceptionHandler { private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); @ExceptionHandler(ServiceException.class) public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) { // 记录关键信息:请求URI、请求方法、用户信息、异常信息 log.error("业务异常 => URI: {}, Method: {}, User: {}, Message: {}", request.getRequestURI(), request.getMethod(), SecurityUtils.getUsername(), e.getMessage()); return R.fail(e.getCode(), e.getMessage()); } @ExceptionHandler(Exception.class) public R<Void> handleException(Exception e, HttpServletRequest request) { // 未知异常记录完整堆栈信息 log.error("系统异常 => URI: {}, Method: {}, User: {}", request.getRequestURI(), request.getMethod(), SecurityUtils.getUsername(), e); // 传入Exception对象,自动记录堆栈 return R.fail("系统异常,请联系管理员"); } }
总结
遵循本规范可以确保:
- ✅ 异常处理统一规范:所有异常都通过全局处理器统一处理
- ✅ 事务一致性保障:业务异常自动触发事务回滚
- ✅ 前端体验友好:返回标准格式的错误信息,便于前端处理
- ✅ 系统安全可靠:避免敏感信息泄露,保障系统安全
- ✅ 问题快速定位:完整的日志记录,便于问题追踪和排查