【问题标题】:Have controller methods to getAll, getById and getByEmail in the same controller while following RESTful API standards在遵循 RESTful API 标准的同时,在同一控制器中拥有 getAll、getById 和 getByEmail 的控制器方法
【发布时间】:2021-03-02 22:05:53
【问题描述】:

我有一个实体Users,其中包含idemailAddress。我想编写一个同时拥有 3 个 get 方法的控制器:

  • getAll() - http://localhost:8080/api/users?pageNumber=0&pageSize=10
  • get(UUID id) - http://localhost:8080/api/users/209347293875928375928
  • get(String email) - http://localhost:8080/api/users/fireball%40email.comhttp://localhost:8080/api/users?emailAddress="fireball%40email.com"

注意:两段代码的区别在于两者中最后一个函数的参数(第一个是@PathVariable,第二个是@RequestParam

我尝试了两种方法来实现这一点(都遇到了单独的问题),第一种是:

@GetMapping
  @ApiOperation(value = "List all Users")
  @ApiResponses({
    @ApiResponse(code = 200, message = "OK", response = User.class, responseContainer = "List"),
    @ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
    @ApiResponse(code = 404, message = "Not Found", response = Error.class),
    @ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
  })
  @Transactional(readOnly = true)
  public ResponseEntity<Page<User>> getAll(
      @RequestParam(required = false, defaultValue = "0") int pageNumber,
      @RequestParam(required = false, defaultValue = "10") int pageSize) {
    return ResponseEntity.ok(UserService.getUsers(pageNumber, pageSize));
  }

  @GetMapping(path = "/{id}")
  @ApiOperation(value = "Get User by ID")
  @Transactional(readOnly = true)
  @ApiResponses({
    @ApiResponse(code = 200, message = "OK", response = User.class),
    @ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
    @ApiResponse(code = 404, message = "Not Found", response = Error.class),
    @ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
  })
  public ResponseEntity<?> get(@PathVariable("id") final UUID id) {

    return UserService.get(id)
        .map(ResponseEntity::ok)
        .map(ResponseEntity.class::cast)
        .orElse(
            ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, id))));
  }
  
  @GetMapping
  @ApiOperation(value = "Get User by Email Address")
  @Transactional(readOnly = true)
  @ApiResponses({
    @ApiResponse(code = 200, message = "OK", response = User.class),
    @ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
    @ApiResponse(code = 404, message = "Not Found", response = Error.class),
    @ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
  })
  public ResponseEntity<?> get(@RequestParam("email") final String email) {

    return UserService.get(email)
        .map(ResponseEntity::ok)
        .map(ResponseEntity.class::cast)
        .orElse(
            ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, email))));
  }

上面的代码在编译时失败,Ambiguous mapping. Cannot map 'userController' method 错误。本质上,getAll()get(@RequestParam("email") final String email) 具有相同的 URL 路径 - /users

第二个:

  @GetMapping
  @ApiOperation(value = "List all Users")
  @ApiResponses({
    @ApiResponse(code = 200, message = "OK", response = User.class, responseContainer = "List"),
    @ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
    @ApiResponse(code = 404, message = "Not Found", response = Error.class),
    @ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
  })
  @Transactional(readOnly = true)
  public ResponseEntity<Page<User>> getAll(
      @RequestParam(required = false, defaultValue = "0") int pageNumber,
      @RequestParam(required = false, defaultValue = "10") int pageSize) {
    return ResponseEntity.ok(UserService.getUsers(pageNumber, pageSize));
  }

  @GetMapping(path = "/{id}")
  @ApiOperation(value = "Get User by ID")
  @Transactional(readOnly = true)
  @ApiResponses({
    @ApiResponse(code = 200, message = "OK", response = User.class),
    @ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
    @ApiResponse(code = 404, message = "Not Found", response = Error.class),
    @ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
  })
  public ResponseEntity<?> get(@PathVariable("id") final UUID id) {

    return UserService.get(id)
        .map(ResponseEntity::ok)
        .map(ResponseEntity.class::cast)
        .orElse(
            ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, id))));
  }
  
  @GetMapping(path = "/{email}")
  @ApiOperation(value = "Get User by Email Address")
  @Transactional(readOnly = true)
  @ApiResponses({
    @ApiResponse(code = 200, message = "OK", response = User.class),
    @ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
    @ApiResponse(code = 404, message = "Not Found", response = Error.class),
    @ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
  })
  public ResponseEntity<?> get(@PathVariable("email") final String email) {

    return UserService.get(email)
        .map(ResponseEntity::ok)
        .map(ResponseEntity.class::cast)
        .orElse(
            ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, email))));
  }

在这里我遇到了控制器无法在get(@PathVariable("email") final String emailget(@PathVariable("id") final UUID id) 之间解析的问题,并出现以下错误:

Ambiguous handler methods mapped for '/api/gems/users/fireball%40email.com': {public org.springframework.http.ResponseEntity com.personal.project.controllers.UserController.get(java.util.UUID), public org.springframework.http.ResponseEntity com.personal.project.controllers.UserController.get(java.lang.String)}

【问题讨论】:

    标签: java spring spring-boot api rest


    【解决方案1】:

    您可以通过将这两个Ambiguous路径合并为一个来解决它。 对于第一种情况:

      @GetMapping
      @ApiOperation(value = "List all Users")
      @ApiResponses({
        @ApiResponse(code = 200, message = "OK", response = User.class, responseContainer = "List"),
        @ApiResponse(code = 401, message = "Unauthorized", response = Error.class),
        @ApiResponse(code = 404, message = "Not Found", response = Error.class),
        @ApiResponse(code = 500, message = "Internal Server Error", response = Error.class)
      })
      @Transactional(readOnly = true)
      public ResponseEntity<?> getUsers(
          @RequestParam(required = false, defaultValue = "0") int pageNumber,
          @RequestParam(required = false, defaultValue = "10") int pageSize,
    // add email as param.
          @RequestParam(required = false) String email,
    
    ) {
        if(email ==null || StringUtils.isEmpty(email)){
           return ResponseEntity.ok(UserService.getUsers(pageNumber, pageSize));
        }else return UserService.get(email)
            .map(ResponseEntity::ok)
            .map(ResponseEntity.class::cast)
            .orElse(
                ResponseEntity.status(NOT_FOUND).body(new Error(format(USER_NOT_FOUND_MESSAGE, email))));
      }
    

    第二个:

     // change the type UUID id to string.
      @GetMapping(path = "/{id}")
      ...
      public ResponseEntity<?> get(@PathVariable("id") final String id) {
        // check if id as an uuid or email, and based on that take action
      }
    

    【讨论】:

      【解决方案2】:

      按照我的previous answer,你只需要两个控制器方法。然后,在处理GET /users请求的方法中,name查询参数的存在与否将决定服务器是获取所有用户还是通过电子邮件过滤用户。

      请参阅下面的示例。为简单起见,我没有添加 OpenAPI 注释并删除了分页,但您可以根据需要轻松添加它们:

      @RestController
      @RequestMapping(path = "/users", 
                      produces = MediaType.APPLICATION_JSON_VALUE)
      public class UserController {
      
          @GetMapping
          public ResponseEntity<List<User>> findUsers(@RequestParam("email") final String email) {
              // If email is null/empty/blank, then fetch all users
              // Otherwise filter users by email
          }
      
          @GetMapping(path = "/{id}")
          public ResponseEntity<User> findUserById(@PathVariable("id") final UUID id) {
              // Find user with the given ID
          }
      }
      

      如果您计划支持多个过滤器(或者只是想避免使用if-'else 或自定义存储库方法),最好使用 Spring Data 支持的Query by Example (QBE)

      Query by Example (QBE) 是一种用户友好的查询技术,具有简单的界面。它允许创建动态查询,并且不需要您编写包含字段名称的查询。事实上,Query by Example 根本不需要您使用商店特定的查询语言来编写查询。

      文档还说明了以下内容:

      Query by Example API 由三部分组成:

      • 探针:具有填充字段的域对象的实际示例。
      • ExampleMatcherExampleMatcher 包含有关如何匹配特定字段的详细信息。它可以在多个Examples 中重复使用。
      • ExampleExample 由探针和ExampleMatcher 组成。它用于创建查询。

      示例查询非常适合多种用例:

      • 使用一组静态或动态约束查询您的数据存储。
      • 频繁重构域对象,无需担心破坏现有查询。
      • 独立于底层数据存储 API 工作。

      Query by Example 也有一些限制:

      • 不支持嵌套或分组属性约束,例如firstname = ?0 or (firstname = ?1 and lastname = ?2)
      • 仅支持字符串的开始/包含/结束/正则表达式匹配和其他属性类型的精确匹配。

      请参阅下面如何在您的案例中使用示例查询:

      @Data
      @Builder
      @NoArgsConstructor
      @AllArgsConstructor
      public class UserFilter {
          private String email;
      }
      
      @RestController
      @RequiredArgsConstructor
      @RequestMapping(path = "/users", 
                      produces = MediaType.APPLICATION_JSON_VALUE)
      public class UserController {
      
          private final UserService userService;
      
          @GetMapping
          public ResponseEntity<List<User>> findUsers(@RequestParam("email") final String email) {
              UserFilter filter = UserFilter.builder().name(email).build();
              List<User> users = userService.findUsers(filter);
              return ResponseEntity.ok(users);
          }
      
          @GetMapping(path = "/{id}")
          public ResponseEntity<User> findUserById(@PathVariable("id") final UUID id) {
              User user = userService.findUserById(filter).orElseThrow(SomeSortOfException::new);
              return ResponseEntity.ok(user); 
          }
      }
      
      @Service
      @RequiredArgsConstructor
      public class UserService {
      
          private final UserRepository userRepository;
      
          public List<User> findUsers(UserFilter filter) {
      
              User user = new User();
              user.setEmail(filter.getEmail());
      
              Example<User> example = Example.of(user);
              return userRepository.findAll(example);
          }
      
          public Optional<User> findUserById(UUID id) {
              return userRepository.findById(id);
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-06-29
        • 2014-11-28
        • 2020-12-06
        • 2015-08-24
        • 2021-07-26
        • 1970-01-01
        • 2014-11-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多