后端API接口规范设计与实践指南 1. 为什么我们需要规范的后端API接口作为一名经历过多个企业级项目的老码农我深刻体会到API接口规范的重要性。记得刚入行时参与的一个电商项目由于缺乏统一的接口规范前后端联调时各种问题层出不穷返回数据结构不一致、错误码混乱、参数校验缺失...这些技术债最终导致项目延期三个月上线。1.1 混乱接口的典型症状先来看几个常见的反面案例// 反例1随意返回数据 GetMapping(/user) public Object getUser() { if(Math.random() 0.5) { return userService.list(); // 返回List } else { return new HashMap(); // 返回Map } } // 反例2裸抛异常 PostMapping(/order) public String createOrder(RequestBody OrderDTO dto) { if(dto.getAmount() null) { throw new RuntimeException(金额不能为空); // 直接抛出RuntimeException } return orderService.create(dto); } // 反例3参数校验缺失 GetMapping(/product) public ProductVO getProduct(Integer id) { // 没有校验id是否为空 return productService.getById(id); }这些代码的问题在于返回数据结构不可预期错误处理方式粗暴参数校验完全依赖业务代码没有统一的异常处理机制1.2 规范接口的核心价值规范的API接口应该具备以下特征一致性所有接口遵循相同的结构和约定可预测性调用方可以准确预知接口行为健壮性能够优雅处理各种异常情况自描述性通过接口本身就能理解其用途// 正例规范的接口示例 GetMapping(/users/{id}) public ResultUserVO getUser(PathVariable Min(1) Long id) { return Result.success(userService.getUserById(id)); }这样的接口使用Result统一包装返回结果通过Min注解明确参数校验规则路径命名符合RESTful规范返回类型明确是UserVO2. 构建规范API的核心组件2.1 统一返回结构设计统一的返回结构应该包含三个基本要素状态码明确操作结果消息可读的提示信息数据实际业务数据Data NoArgsConstructor AllArgsConstructor public class ResultT { private Integer code; private String message; private T data; // 成功响应 public static T ResultT success(T data) { return new Result(200, 成功, data); } // 失败响应 public static T ResultT fail(Integer code, String message) { return new Result(code, message, null); } }实际项目中我们可以进一步优化这个结构使用枚举管理状态码public enum ResultCode { SUCCESS(200, 操作成功), BAD_REQUEST(400, 参数错误), UNAUTHORIZED(401, 未授权), FORBIDDEN(403, 禁止访问), NOT_FOUND(404, 资源不存在), SERVER_ERROR(500, 服务器错误); private final Integer code; private final String message; // constructor and getters }支持链式调用public ResultT code(Integer code) { this.code code; return this; } public ResultT message(String message) { this.message message; return this; }添加扩展字段private MapString, Object extra; // 额外信息 public ResultT addExtra(String key, Object value) { if(extra null) { extra new HashMap(); } extra.put(key, value); return this; }2.2 自动统一包装手动包装每个返回结果既繁琐又容易遗漏我们可以利用Spring的ResponseBodyAdvice实现自动包装RestControllerAdvice public class ResponseAdvice implements ResponseBodyAdviceObject { // 排除不需要包装的返回类型 Override public boolean supports(MethodParameter returnType, Class? extends HttpMessageConverter? converterType) { return !returnType.getParameterType().equals(Result.class); } Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class? extends HttpMessageConverter? selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { // 处理String类型特殊处理 if(body instanceof String) { return JSON.toJSONString(Result.success(body)); } // 已经包装过的直接返回 if(body instanceof Result) { return body; } return Result.success(body); } }注意事项需要单独处理String类型返回值因为String有专门的HttpMessageConverter可以通过注解排除特定接口的自动包装对于文件下载等特殊接口需要额外处理2.3 完善的参数校验参数校验是API可靠性的第一道防线。Spring提供了强大的校验机制2.3.1 声明式校验Data public class UserDTO { NotBlank(message 用户名不能为空) Size(min 2, max 20, message 用户名长度2-20个字符) private String username; NotNull(message 年龄不能为空) Min(value 1, message 年龄最小1岁) Max(value 150, message 年龄最大150岁) private Integer age; Email(message 邮箱格式不正确) private String email; Pattern(regexp 1[3-9]\\d{9}, message 手机号格式不正确) private String mobile; }2.3.2 分组校验不同场景可能需要不同的校验规则public interface CreateGroup {} // 创建时校验组 public interface UpdateGroup {} // 更新时校验组 Data public class ProductDTO { Null(groups CreateGroup.class, message 创建时ID必须为空) NotNull(groups UpdateGroup.class, message 更新时ID不能为空) private Long id; NotBlank(groups {CreateGroup.class, UpdateGroup.class}) private String name; } // 使用分组校验 PostMapping public Result create(Validated(CreateGroup.class) RequestBody ProductDTO dto) { // ... }2.3.3 自定义校验当内置注解不能满足需求时可以自定义校验规则Target({ElementType.FIELD}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy EnumValueValidator.class) public interface EnumValue { String message() default 值不在指定范围内; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; Class? extends Enum? enumClass(); // 枚举类 String enumMethod() default name; // 校验方法 } public class EnumValueValidator implements ConstraintValidatorEnumValue, Object { private Class? extends Enum? enumClass; private String enumMethod; Override public void initialize(EnumValue constraintAnnotation) { enumClass constraintAnnotation.enumClass(); enumMethod constraintAnnotation.enumMethod(); } Override public boolean isValid(Object value, ConstraintValidatorContext context) { if(value null) return true; try { // 反射调用枚举的校验方法 Method method enumClass.getMethod(enumMethod); for(Enum? enumVal : enumClass.getEnumConstants()) { if(value.equals(method.invoke(enumVal))) { return true; } } return false; } catch (Exception e) { throw new RuntimeException(e); } } }使用自定义注解public enum Gender { MALE, FEMALE; } Data public class PersonDTO { EnumValue(enumClass Gender.class, message 性别只能是MALE或FEMALE) private String gender; }2.4 异常处理机制良好的异常处理应该做到业务异常与系统异常分离异常信息友好可读异常处理集中管理2.4.1 自定义异常体系// 基础业务异常 public class BusinessException extends RuntimeException { private final Integer code; public BusinessException(Integer code, String message) { super(message); this.code code; } public BusinessException(ResultCode resultCode) { super(resultCode.getMessage()); this.code resultCode.getCode(); } } // 具体业务异常 public class UserNotFoundException extends BusinessException { public UserNotFoundException() { super(ResultCode.USER_NOT_FOUND); } } public class PermissionDeniedException extends BusinessException { public PermissionDeniedException() { super(ResultCode.PERMISSION_DENIED); } }2.4.2 全局异常处理RestControllerAdvice public class GlobalExceptionHandler { // 处理业务异常 ExceptionHandler(BusinessException.class) public ResultVoid handleBusinessException(BusinessException e) { log.error(业务异常: {}, e.getMessage(), e); return Result.fail(e.getCode(), e.getMessage()); } // 处理参数校验异常 ExceptionHandler(MethodArgumentNotValidException.class) public ResultVoid handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { String message e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .collect(Collectors.joining(, )); return Result.fail(ResultCode.BAD_REQUEST.getCode(), message); } // 处理系统异常 ExceptionHandler(Exception.class) public ResultVoid handleException(Exception e) { log.error(系统异常: {}, e.getMessage(), e); return Result.fail(ResultCode.SERVER_ERROR); } }2.4.3 异常处理最佳实践异常分类处理不同类型的异常应该有不同的处理方式异常信息国际化根据请求头Accept-Language返回对应语言的错误信息异常日志记录关键业务异常需要记录详细日志异常重试机制对于可重试的异常自动重试// 带重试机制的异常处理示例 Retryable(value {RemoteAccessException.class}, maxAttempts 3, backoff Backoff(delay 1000)) public ResultString callRemoteService() { // 调用远程服务 return remoteService.call(); } Recover public ResultString recover(RemoteAccessException e) { return Result.fail(ResultCode.REMOTE_SERVICE_ERROR); }3. 高级API设计技巧3.1 接口版本管理随着业务发展API可能需要变更。良好的版本管理策略可以平滑过渡URL路径版本控制/api/v1/users /api/v2/users请求头版本控制Accept: application/vnd.myapi.v1json实现方案RestController RequestMapping(/api/v{version}/users) public class UserController { GetMapping public ResultListUserVO getUsers( PathVariable String version, RequestParam(required false) String name) { if(1.equals(version)) { // v1版本逻辑 } else if(2.equals(version)) { // v2版本逻辑 } // ... } }3.2 接口文档自动化手动维护接口文档容易过时推荐使用自动化工具Swagger集成Configuration EnableOpenApi public class SwaggerConfig { Bean public Docket api() { return new Docket(DocumentationType.OAS_30) .select() .apis(RequestHandlerSelectors.basePackage(com.example.controller)) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title(API文档) .description(系统接口文档) .version(1.0) .build(); } }接口注释规范Operation(summary 获取用户列表) ApiResponses({ ApiResponse(responseCode 200, description 成功), ApiResponse(responseCode 500, description 服务器错误) }) GetMapping(/users) public ResultListUserVO getUsers( Parameter(description 用户名模糊查询) RequestParam(required false) String name, Parameter(description 页码) RequestParam(defaultValue 1) Integer pageNum, Parameter(description 每页数量) RequestParam(defaultValue 10) Integer pageSize) { // ... }3.3 接口安全设计认证与授权GetMapping(/users/me) public ResultUserVO getCurrentUser(AuthenticationPrincipal UserDetails user) { // 获取当前登录用户信息 return Result.success(userService.getById(user.getUsername())); }接口限流RateLimiter(value 10, key #userId) // 每秒10次 GetMapping(/users/{userId}/orders) public ResultListOrderVO getUserOrders(PathVariable String userId) { // ... }敏感数据脱敏Data public class UserVO { private String username; JsonSerialize(using SensitiveSerializer.class) private String mobile; // 手机号脱敏 JsonSerialize(using SensitiveSerializer.class) private String idCard; // 身份证脱敏 } public class SensitiveSerializer extends JsonSerializerString { Override public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { // 实现脱敏逻辑 gen.writeString(value.replaceAll((\\d{3})\\d{4}(\\d{4}), $1****$2)); } }3.4 性能优化技巧响应数据过滤JsonFilter(userFilter) Data public class UserDetailVO { private String username; private String email; private String mobile; // ... } GetMapping(/users/{id}) public MappingJacksonValue getUser(PathVariable Long id, RequestParam(required false) String fields) { UserDetailVO user userService.getDetailById(id); MappingJacksonValue result new MappingJacksonValue(Result.success(user)); if(StringUtils.hasText(fields)) { FilterProvider filters new SimpleFilterProvider() .addFilter(userFilter, SimpleBeanPropertyFilter.filterOutAllExcept(fields.split(,))); result.setFilters(filters); } return result; }批量接口设计PostMapping(/users/batch) public ResultBatchResult batchCreateUsers(RequestBody ListValid UserDTO users) { // 批量处理逻辑 return Result.success(userService.batchCreate(users)); } Data public class BatchResult { private int successCount; private int failCount; private ListErrorItem errors; Data AllArgsConstructor public static class ErrorItem { private int index; private String message; } }异步接口设计PostMapping(/report) public ResultString generateReport() { String taskId UUID.randomUUID().toString(); CompletableFuture.runAsync(() - reportService.generate(taskId)); return Result.success(taskId); } GetMapping(/report/{taskId}) public ResultReportVO getReport(PathVariable String taskId) { return Result.success(reportService.getResult(taskId)); }4. 实战完整API设计示例4.1 用户管理APIRestController RequestMapping(/api/v1/users) Tag(name 用户管理, description 用户相关操作接口) public class UserController { Autowired private UserService userService; Operation(summary 获取用户列表) GetMapping public ResultPageResultUserVO listUsers( Parameter(description 用户名) RequestParam(required false) String username, Parameter(description 页码) RequestParam(defaultValue 1) Integer pageNum, Parameter(description 每页数量) RequestParam(defaultValue 10) Integer pageSize) { PageQuery query new PageQuery(pageNum, pageSize); PageResultUserVO result userService.listUsers(username, query); return Result.success(result); } Operation(summary 获取用户详情) GetMapping(/{userId}) public ResultUserDetailVO getUserDetail( Parameter(description 用户ID) PathVariable Min(1) Long userId) { return Result.success(userService.getDetailById(userId)); } Operation(summary 创建用户) PostMapping public ResultLong createUser( Parameter(description 用户信息) Valid RequestBody UserCreateDTO dto) { return Result.success(userService.createUser(dto)); } Operation(summary 更新用户) PutMapping(/{userId}) public ResultVoid updateUser( Parameter(description 用户ID) PathVariable Min(1) Long userId, Parameter(description 用户信息) Valid RequestBody UserUpdateDTO dto) { userService.updateUser(userId, dto); return Result.success(); } Operation(summary 删除用户) DeleteMapping(/{userId}) public ResultVoid deleteUser( Parameter(description 用户ID) PathVariable Min(1) Long userId) { userService.deleteUser(userId); return Result.success(); } }4.2 相关DTO定义Data EqualsAndHashCode(callSuper true) public class PageResultT extends ResultListT { private Long total; private Integer pageNum; private Integer pageSize; public PageResult(ListT data, Long total, Integer pageNum, Integer pageSize) { super(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data); this.total total; this.pageNum pageNum; this.pageSize pageSize; } } Data public class UserCreateDTO { NotBlank(message 用户名不能为空) Size(min 2, max 20, message 用户名长度2-20个字符) private String username; NotBlank(message 密码不能为空) Size(min 6, max 20, message 密码长度6-20个字符) private String password; NotNull(message 年龄不能为空) Min(value 1, message 年龄最小1岁) Max(value 150, message 年龄最大150岁) private Integer age; Email(message 邮箱格式不正确) private String email; Pattern(regexp 1[3-9]\\d{9}, message 手机号格式不正确) private String mobile; } Data public class UserUpdateDTO { NotNull(message 年龄不能为空) Min(value 1, message 年龄最小1岁) Max(value 150, message 年龄最大150岁) private Integer age; Email(message 邮箱格式不正确) private String email; Pattern(regexp 1[3-9]\\d{9}, message 手机号格式不正确) private String mobile; }4.3 服务层实现Service Slf4j public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override Transactional(readOnly true) public PageResultUserVO listUsers(String username, PageQuery query) { PageHelper.startPage(query.getPageNum(), query.getPageSize()); ListUser users userMapper.selectByUsername(username); PageInfoUser pageInfo new PageInfo(users); ListUserVO userVOs users.stream() .map(this::convertToVO) .collect(Collectors.toList()); return new PageResult(userVOs, pageInfo.getTotal(), query.getPageNum(), query.getPageSize()); } Override Transactional(readOnly true) public UserDetailVO getDetailById(Long userId) { User user userMapper.selectById(userId); if(user null) { throw new UserNotFoundException(); } return convertToDetailVO(user); } Override Transactional public Long createUser(UserCreateDTO dto) { if(userMapper.existsByUsername(dto.getUsername())) { throw new BusinessException(ResultCode.USER_EXISTS); } User user convertToEntity(dto); userMapper.insert(user); return user.getId(); } Override Transactional public void updateUser(Long userId, UserUpdateDTO dto) { User user userMapper.selectById(userId); if(user null) { throw new UserNotFoundException(); } BeanUtils.copyProperties(dto, user); userMapper.updateById(user); } Override Transactional public void deleteUser(Long userId) { if(!userMapper.existsById(userId)) { throw new UserNotFoundException(); } userMapper.deleteById(userId); } // 转换方法省略... }5. 常见问题与解决方案5.1 参数校验常见问题问题1校验注解不生效可能原因没有在Controller方法参数前加Valid或Validated注解校验的DTO类没有使用Data或没有getter/setter方法校验的字段是private且没有提供getter方法解决方案PostMapping public Result create(Valid RequestBody UserDTO dto) { // 确保有Valid或Validated // ... } Data // 确保有Data或手动实现getter/setter public class UserDTO { NotBlank private String username; // 确保不是final字段 }问题2国际化消息不生效配置步骤创建ValidationMessages.properties文件配置MessageSource在注解中使用{}引用消息key# ValidationMessages.properties user.name.notblank用户名不能为空 user.name.size用户名长度必须在{min}到{max}之间Data public class UserDTO { NotBlank(message {user.name.notblank}) Size(min 2, max 20, message {user.name.size}) private String username; }5.2 异常处理常见问题问题1自定义异常没有被全局处理器捕获可能原因异常没有被抛出到Controller层在Service内部被捕获全局异常处理器没有扫描到对应的包异常类型不匹配解决方案// 确保异常抛出到Controller Service public class UserService { public User getById(Long id) { User user userRepository.findById(id); if(user null) { throw new UserNotFoundException(); // 不要捕获这个异常 } return user; } } // 确保全局处理器扫描正确包 RestControllerAdvice(basePackages com.example.controller) public class GlobalExceptionHandler { ExceptionHandler(UserNotFoundException.class) public Result handleUserNotFound(UserNotFoundException e) { // ... } }问题2异常信息暴露敏感数据错误做法try { // 调用第三方服务 } catch (Exception e) { throw new BusinessException(调用XX服务失败 e.getMessage()); // 暴露底层错误 }正确做法try { // 调用第三方服务 } catch (Exception e) { log.error(调用XX服务失败, e); // 记录完整日志 throw new BusinessException(ResultCode.THIRD_PARTY_ERROR); // 返回友好提示 }5.3 性能优化常见问题问题1N1查询问题典型场景public ListOrderVO getUserOrders(Long userId) { ListOrder orders orderMapper.selectByUserId(userId); // 1次查询 return orders.stream() .map(order - { User user userMapper.selectById(order.getUserId()); // N次查询 return convertToVO(order, user); }) .collect(Collectors.toList()); }解决方案使用JOIN查询SELECT o.*, u.name as user_name FROM orders o JOIN users u ON o.user_id u.id WHERE o.user_id #{userId}使用MyBatis的关联查询resultMap idorderWithUser typeOrderVO id propertyid columnid/ result propertyorderNo columnorder_no/ association propertyuser javaTypeUserVO id propertyid columnuser_id/ result propertyname columnuser_name/ /association /resultMap select idselectWithUser resultMaporderWithUser SELECT o.*, u.name as user_name FROM orders o JOIN users u ON o.user_id u.id WHERE o.user_id #{userId} /select使用批量查询public ListOrderVO getUserOrders(Long userId) { ListOrder orders orderMapper.selectByUserId(userId); ListLong userIds orders.stream() .map(Order::getUserId) .distinct() .collect(Collectors.toList()); MapLong, User userMap userMapper.selectByIds(userIds).stream() .collect(Collectors.toMap(User::getId, Function.identity())); return orders.stream() .map(order - convertToVO(order, userMap.get(order.getUserId()))) .collect(Collectors.toList()); }问题2大结果集内存溢出错误做法GetMapping(/export) public ListUser exportAllUsers() { return userMapper.selectAll(); // 可能返回百万条数据 }解决方案使用分页查询使用流式处理使用异步导出GetMapping(/export) public void exportAllUsers(HttpServletResponse response) throws IOException { response.setContentType(application/octet-stream); response.setHeader(Content-Disposition, attachment; filenameusers.csv); try(OutputStream out response.getOutputStream(); CSVPrinter printer new CSVPrinter(new OutputStreamWriter(out), CSVFormat.DEFAULT)) { int pageNum 1; int pageSize 1000; PageResultUser page; do { page userService.listUsers(null, new PageQuery(pageNum, pageSize)); for(User user : page.getData()) { printer.printRecord(user.getId(), user.getName(), user.getEmail()); } pageNum; } while(page.getData().size() pageSize); } }6. 接口设计最佳实践6.1 RESTful设计原则资源命名使用名词复数形式/users 而不是 /getUsers避免动词/users/{id}/activation 而不是 /activateUserHTTP方法使用GET获取资源POST创建资源PUT全量更新资源PATCH部分更新资源DELETE删除资源状态码使用200 OK成功请求201 Created资源创建成功204 No Content成功但无返回内容400 Bad Request客户端错误401 Unauthorized未认证403 Forbidden无权限404 Not Found资源不存在500 Internal Server Error服务器错误6.2 分页查询规范标准分页响应结构{ code: 200, message: 成功, data: [ {id: 1, name: 用户1}, {id: 2, name: 用户2} ], total: 100, pageNum: 1, pageSize: 10 }实现方案Data public class PageQuery { private Integer pageNum; private Integer pageSize; public PageQuery(Integer pageNum, Integer pageSize) { this.pageNum pageNum null || pageNum 1 ? 1 : pageNum; this.pageSize pageSize null || pageSize 1 ? 10 : pageSize; } public E PageE toPage() { return Page.of(pageNum, pageSize); } } public interface PageT extends ListT { static T PageT of(int pageNum, int pageSize) { return PageHelper.startPage(pageNum, pageSize); } }6.3 批量操作设计批量创建PostMapping(/batch) public ResultBatchResult batchCreate(Valid RequestBody ListValid UserCreateDTO dtos) { return Result.success(userService.batchCreate(dtos)); }批量更新PutMapping(/batch) public ResultBatchResult batchUpdate(Valid RequestBody ListValid UserUpdateDTO dtos) { return Result.success(userService.batchUpdate(dtos)); }批量删除DeleteMapping(/batch) public ResultVoid batchDelete(RequestParam ListLong ids) { userService.batchDelete(ids); return Result.success(); }6.4 接口幂等性设计方案1Token机制GetMapping(/token) public ResultString createToken() { String token UUID.randomUUID().toString(); redisTemplate.opsForValue().set(token, 1, 5, TimeUnit.MINUTES); return Result.success(token); } PostMapping(/order) public ResultLong createOrder(RequestHeader(X-Idempotent-Token) String token) { if(!redisTemplate.delete(token)) { throw new BusinessException(ResultCode.REPEAT_REQUEST); } return Result.success(orderService.create()); }方案2唯一索引Data public class OrderCreateDTO { NotBlank private String orderNo; // 唯一订单号 // 其他字段 } Service public class OrderService { Transactional public Long create(OrderCreateDTO dto) { if(orderMapper.existsByOrderNo(dto.getOrderNo())) { throw new BusinessException(ResultCode.ORDER_EXISTS); } // 创建订单 } }方案3状态机Service public class OrderService { Transactional public void pay(Long orderId) { Order order orderMapper.selectById(orderId); if(order.getStatus() ! OrderStatus.CREATED) { throw new BusinessException(ResultCode.ORDER_STATUS_ERROR); } order.setStatus(OrderStatus.PAID); orderMapper.updateById(order); } }7. 接口测试策略7.1 单元测试Controller层测试WebMvcTest(UserController.class) AutoConfigureMockMvc class UserControllerTest { Autowired private MockMvc mockMvc; MockBean private UserService userService; Test void getUserById() throws Exception { UserVO user new UserVO(1L, test); when(userService.getById(1L)).thenReturn(user); mockMvc.perform(get(/users/1) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath($.code).value(200)) .andExpect(jsonPath($.data.id).value(1)) .andExpect(jsonPath($.data.username).value(test)); } }7.2 集成测试SpringBootTest AutoConfigureMockMvc class UserIntegrationTest { Autowired private MockMvc mockMvc; Autowired private UserMapper userMapper; Test Transactional Rollback void createUser() throws Exception { UserCreateDTO dto new UserCreateDTO(); dto.setUsername(test); dto.setPassword(123456); dto.setAge(20); mockMvc.perform(post(/users) .contentType(MediaType.APPLICATION_JSON) .content(JSON.toJSONString(dto))) .andExpect(status().isOk()) .andExpect(jsonPath($.code).value(200)) .andExpect(jsonPath($.data).isNumber()); } }7.3 契约测试使用Pact进行契约测试消费者端测试RunWith(PactRunner.class) Provider(userService) Consumer(orderService) public class UserContractTest { Pact(consumer orderService) public RequestResponsePact getUserById(PactDslWithProvider builder) { return builder .given(user exists) .uponReceiving(get user by id) .path(/users/1) .method(GET) .willRespondWith() .status(200) .body(new PactDslJsonBody() .integerType(code, 200) .stringType(message, 成功) .object(data) .integerType(id, 1) .stringType(username, test) .closeObject()) .toPact(); } Test PactVerification(fragment getUserById) public void testGetUserById() { UserClient client new UserClient(http://localhost:8080); User user client.getUserById(1L); assertThat(user.getId()).isEqualTo(1L); assertThat(user.getUsername()).isEqualTo(test); } }提供者端验证RunWith(PactRunner.class) Provider(userService) PactFolder(pacts) public class UserProviderTest { TestTarget public final Target target new HttpTarget(8080); State(user exists) public void userExists() { // 准备测试数据 User user new User(1L, test); userRepository.save(user); } }7.4 性能测试使用JMeter进行性能测试测试计划线程组模拟并发