如何设计出优秀、健壮且易于维护的API——关于HTTP状态码与业务逻辑状态码的处理

反例

刚开始学习后端开发时做了一个文件管理系统,其中有一个文件删除的API。
文件和目录删除操作 Delete 接口的设计的很粗糙 :HTTP状态码和业务逻辑状态码都返回给请求方了。
业务逻辑状态码:删除操作执行成功逻辑状态码code=200,删除失败逻辑状态码code=500;文件不存在code=FS4004;无管理权限code=FS4003.
http状态码:只要请求成功就返回200。
这就导致了一个问题,因为我的处理逻辑比较复杂,请求方每次读到http状态码为200就认为删除成功了。
但是我们测试删除操作时,并不存在的文件时也显示http code=200,但我们更想给请求方更多的信息。

正确的处理方法

第1步:在业务逻辑层定义并抛出“业务异常”

当删除操作失败时,不要直接返回一个包含错误码的普通对象,而是抛出一个能描述业务错误的自定义异常。

点击查看代码
// 你的文件删除服务 (FileService.java)
public void deleteFile(String fileId, String userId) {
    File file = fileRepository.findById(fileId);

    // 业务场景1:文件不存在
    if (file == null) {
        // 抛出业务异常,而不是返回一个错误对象
        throw new BusinessException("FS4004", "文件不存在");
    }

    // 业务场景2:权限不足
    if (!file.getOwnerId().equals(userId)) {
        throw new BusinessException("FS4003", "无权删除该文件");
    }

    // ... 执行删除操作
    boolean deleted = doDelete(file);

    // 业务场景3:未知的内部错误导致删除失败
    if (!deleted) {
        throw new BusinessException("SYS5000", "删除操作失败,请联系管理员");
    }
}

第2步:在最外层创建“全局异常处理器”

在你的 trigger 层(通常是 Spring MVC 中),创建一个全局异常处理器。它的职责就是捕获从业务层抛出的 BusinessException,然后把它“翻译”成标准的 HTTP 响应。

点击查看代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ApiResponse> handleBusinessException(BusinessException ex) {
        ApiResponse responseBody = new ApiResponse(ex.getCode(), ex.getMessage(), null);

        // 根据业务异常的类型,决定返回哪个HTTP状态码
        switch (ex.getCode()) {
            case "FS4004": // 文件不存在
                return new ResponseEntity<>(responseBody, HttpStatus.NOT_FOUND); // HTTP 404
            case "FS4003": // 无权限
                return new ResponseEntity<>(responseBody, HttpStatus.FORBIDDEN); // HTTP 403
            default: // 其他业务失败,如删除失败
                return new ResponseEntity<>(responseBody, HttpStatus.INTERNAL_SERVER_ERROR); // HTTP 500
        }
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiResponse> handleGenericException(Exception ex) {
        // 处理所有未被捕获的未知异常
        ApiResponse responseBody = new ApiResponse("SYS9999", "系统内部未知错误", null);
        return new ResponseEntity<>(responseBody, HttpStatus.INTERNAL_SERVER_ERROR); // HTTP 500
    }

}
#### 第3步:Controller 保持简洁 做了以上两步后,Controller 会变得非常干净,它只需要调用业务逻辑,完全不用关心成功失败的判断和响应封装。
点击查看代码
@RestController
public class FileController {

    @Autowired
    private FileService fileService;

    @DeleteMapping("/files/{id}")
    public ResponseEntity<ApiResponse> deleteFile(@PathVariable String id) {
        // 只管调用,成功了就往下走,失败了异常处理器会接管
        fileService.deleteFile(id, getCurrentUserId());

        // 如果代码能执行到这里,说明没有任何异常,删除成功
        ApiResponse responseBody = new ApiResponse("200", "删除成功", null);
        return new ResponseEntity<>(responseBody, HttpStatus.OK); // HTTP 200
       
    }
}

这不仅能让自己的代码更易于维护,也能让所有调用你接口的客户端(前端、其他后端服务等)更容易地进行合作开发。

最佳实践

一个优秀的 API 设计,应该让这两者协同工作,而不是混淆它们。
核心流程:

业务层:只关心业务逻辑,当业务失败时,抛出携带业务状态码的自定义异常(如 AppException )。

表现层(全局异常处理器):作为“翻译官”,捕获业务异常,然后将业务状态码映射到一个最贴切的 HTTP 状态码,并构建包含业务状态码的 JSON 响应体。

posted @ 2025-10-02 17:21  浪矢-CL  阅读(15)  评论(0)    收藏  举报