← 返回首页
🔌

RESTful API设计

📂 architecture ⏱ 1 min 196 words

RESTful API设计

资源建模

RESTful API以资源为中心,通过URI标识资源,HTTP方法表示操作。

@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    @GetMapping("/{id}")
    public ResponseEntity<UserDTO> getUser(@PathVariable Long id) {
        return ResponseEntity.ok(userService.findById(id));
    }

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserDTO user = userService.create(request);
        URI location = URI.create("/api/v1/users/" + user.getId());
        return ResponseEntity.created(location).body(user);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserDTO> updateUser(@PathVariable Long id,
                                              @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

HATEOAS超媒体

通过HATEOAS让API响应包含相关操作的链接,实现自描述性。

@RestController
@RequestMapping("/api/v1/users")
public class UserController {

    @GetMapping("/{id}")
    public EntityModel<UserDTO> getUser(@PathVariable Long id) {
        UserDTO user = userService.findById(id);
        EntityModel<UserDTO> model = EntityModel.of(user);
        model.add(linkTo(methodOn(UserController.class).getUser(id)).withSelfRel());
        model.add(linkTo(methodOn(UserController.class).listUsers()).withRel("users"));
        model.add(linkTo(methodOn(UserController.class).deleteUser(id)).withRel("delete"));
        return model;
    }
}

状态码规范

正确使用HTTP状态码表达API语义。

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse("NOT_FOUND", e.getMessage()));
    }

    @ExceptionHandler(ValidationException.class)
    public ResponseEntity<ErrorResponse> handleValidation(ValidationException e) {
        return ResponseEntity.badRequest()
            .body(new ErrorResponse("VALIDATION_ERROR", e.getMessage()));
    }

    @ExceptionHandler(DuplicateResourceException.class)
    public ResponseEntity<ErrorResponse> handleConflict(DuplicateResourceException e) {
        return ResponseEntity.status(HttpStatus.CONFLICT)
            .body(new ErrorResponse("CONFLICT", e.getMessage()));
    }
}

分页与过滤

统一的分页响应格式和过滤查询参数设计。

@GetMapping
public ResponseEntity<PagedModel<UserDTO>> listUsers(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size,
        @RequestParam(required = false) String sort) {

    PageRequest pageRequest = PageRequest.of(page, size, Sort.by(sort));
    Page<UserDTO> users = userService.findAll(pageRequest);

    PagedModel.PageMetadata metadata = new PagedModel.PageMetadata(
        size, page, users.getTotalElements(), users.getTotalPages());
    PagedModel<UserDTO> pagedModel = PagedModel.of(users.getContent(), metadata);

    return ResponseEntity.ok(pagedModel);
}