在 Spring Boot 中构造 API 响应的最佳实践
在平时的开发和项目中,我们一定会涉及到接口对接的功能,由于不同开发人员的编码习惯不同,API报文在项目中通常是"百花齐放"的。
不但增加工作难度,往往也是扯皮的大头,如果能统一报文格式,不但能减少沟通成本,同时也可以减少并行开发的难度,今天我们就来介绍一种在项目中常用的API响应最佳实践,快快@你的同事,看完以后,下次对接我们直接“打默认”
01 / 为什么好的API报文结构很重要
- 优化客户端的错误提示和处理逻辑,前端小姐姐看到了会请你吃饭
- 减少调试时的工作量,可以更快的发现问题,避免加班引起严重脱发
- 增加代码的可读性和维护性,避免code review的时候被老大骂
02 / 什么才是良好的API响应
结构良好的 API 响应应该是:
-
一致:不同端点之间的格式一致。
-
信息足够有效:包括相关数据、消息、状态代码和错误代码。
-
简单:易于解析和理解。
03 / 如何制定良好的结构
定义标准响应格式
首先创建所有 API 都将遵循的标准响应格式。这是一个简单而有效的格式:
在此结构中, errorCode作为专门用于应用程序级错误的字段。需要注意的是,这并不意味着要取代标准 HTTP 状态代码,如200 OK 、 404 Not Found等。
相反,我们更推荐使用 HTTP 状态代码来维护标准协议级错误处理。
当应用程序需要处理更细粒度的、与业务相关的错误场景时, errorCode就会发挥作用。
例如,考虑用户的请求违反业务规则的情况 - 即使从语法或结构的角度来看该请求可能是有效的,但这应该导致400 Bad Request HTTP 状态,因为输入不符合应用程序的业务规则。
在这种情况下, errorCode将从业务逻辑的角度提供有关到底出了什么问题的更多详细信息。
例如,如果用户提交缺货产品的订单,系统将响应400 Bad Request状态并提供errorCode ,例如2000 (商品缺货)。
这使得 UI 或使用 API 的任何客户端能够通过显示适当的错误消息或提示用户采取特定操作(例如,“该产品不再可用”),以更明智和一致的方式处理情况。
这种方法确保HTTP 状态代码反映协议级别请求的总体结果,而errorCode提供用于处理应用程序内特定业务逻辑错误的附加信息。
它还简化了系统不同层的错误处理,因为errorCode可用于触发 UI 或使用 API 的任何其他服务中的特定操作。
每个字段解释:
success:
-
类型:boolean
-
描述:指示 API 调用是否成功。
-
功能:快速确定请求的结果,简化客户端逻辑。
message:
-
类型: String
-
描述:提供有关 API 调用结果的人类可读消息。
-
功能:有助于向客户提供上下文反馈,对于成功和错误场景都很有用。
data:
-
类型: T
-
描述:包含响应的有效负载,可以是任何数据类型。
-
功能:提供客户请求的实际数据。
errors:
-
类型:List<String>
-
描述:API 调用不成功时的错误消息列表。
-
功能:提供有关出错原因的详细信息,对于调试和用户反馈很有用。
errorCode:
-
类型:int
-
描述:表示错误类型的特定代码。
-
功能:有助于以编程方式对错误进行分类并做出适当的响应。这是针对应用程序级别的错误,可能与任何业务流程错误相关,可能需要在 UI 层进行更多处理。所以这会很有帮助。但对于错误代码,我们应该始终优先考虑 HTTP 状态代码。
timestamp:
-
类型: long
-
描述:生成响应时的时间戳。
-
功能:对于记录和跟踪响应时间很有用,这可以帮助调试和监控。
path:
-
类型:String
-
描述:被调用的 API 端点。
-
功能:帮助识别哪个 API 端点生成了响应,对于调试和日志记录很有用。
04 / 创建实用的响应处理方法
public class ResponseUtil {
public static <T> ApiResponse<T> success(T data, String message, String path) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(true);
response.setMessage(message);
response.setData(data);
response.setErrors(null);
response.setErrorCode(0); // No application-level error, success scenario
response.setTimestamp(System.currentTimeMillis());
response.setPath(path);
return response;
}
public static <T> ApiResponse<T> error(List<String> errors, String message, int errorCode, String path) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(false);
response.setMessage(message);
response.setData(null);
response.setErrors(errors);
response.setErrorCode(errorCode); // Application-specific error code
response.setTimestamp(System.currentTimeMillis());
response.setPath(path);
return response;
}
public static <T> ApiResponse<T> error(String error, String message, int errorCode, String path) {
return error(Arrays.asList(error), message, errorCode, path);
}
}
04 / 实现全局异常处理
全局处理异常可确保捕获任何未处理的错误并以标准响应格式返回。使用@ControllerAdvice和@ExceptionHandler注释。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException(HttpServletRequest request, Exception ex) {
List<String> errors = Arrays.asList(ex.getMessage());
ApiResponse<Void> response = ResponseUtil.error(errors, "An error occurred", 1000,
return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleResourceNotFoundException(HttpServletRequest request, ResourceNotFoundException ex) {
ApiResponse<Void> response = ResponseUtil.error(ex.getMessage(), "Resource not found", 1001,
return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ValidationException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationException(HttpServletRequest request, ValidationException ex) {
ApiResponse<Void> response = ResponseUtil.error(ex.getErrors(), "Validation failed", 1002,
return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
}
// Handle other specific exceptions similarly
}
05 / 在controller中使用响应格式
@RestController
@RequestMapping("/api/products")
public class ProductController {
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<Product>> getProductById(@PathVariable Long id, HttpServletRequest request) {
// Fetch product by id (dummy code)
Product product = productService.findById(id);
if (product == null) {
throw new ResourceNotFoundException("Product not found with id " + id);
}
ApiResponse<Product> response = ResponseUtil.success(product, "Product fetched successfully", request.getRequestURI());
// Success response, no application-level error
return new ResponseEntity<>(response, HttpStatus.OK);
}
@PostMapping
public ResponseEntity<ApiResponse<Product>> createProduct(@RequestBody Product product, HttpServletRequest request) {
// Create new product (dummy code)
Product createdProduct = productService.save(product);
ApiResponse<Product> response = ResponseUtil.success(createdProduct, "Product created successfully", request.getRequestURI());
// Success response, no application-level error
return new ResponseEntity<>(response, HttpStatus.CREATED);
}
// More endpoints...
}
06/ 定义统一的错误代码
统一的错误代码,可以避免重复的沟通,直接记录在文档中,或者保存在统一的配置中心中,上下游需要使用的时候可以直接查阅。
同时定义错误代码的过程,也是对自己逻辑重新梳理的过程,避免在编写时遗漏了边界情况Error Code Description
2000 Item out of stock
2001 Payment method declined
2002 Invalid coupon code
2003 Order cancellation period expired
2004 Account temporarily suspended
2005 Multiple orders detected for the same productmark
07 / 总结
完成了这些工作,你就得到一个近乎完美的API报文结构,它干净整洁,易于维护。同时减少了巨大的沟通成本,当然好的规范是需要团队共同执行的,多和你的同事沟通,相信你们的合作会越来越顺利的