Java SpringRESTAPI多请求参数与控制器实现

Java SpringRESTAPI多请求参数与控制器实现,java,spring,rest,api,spring-boot,Java,Spring,Rest,Api,Spring Boot,我想知道在给定多个请求参数的GET请求的情况下,控制器的正确实现方式。在我对REST的理解中,使用一个端点和额外的过滤/排序参数要比使用多个端点(每种情况一个端点)要好得多。我只是想知道这种端点的维护性和可扩展性。请查看下面的示例: @RestController @RequestMapping("/customers") public class CustomerController { @Autowired private CustomerRepository custom

我想知道在给定多个请求参数的GET请求的情况下,控制器的正确实现方式。在我对REST的理解中,使用一个端点和额外的过滤/排序参数要比使用多个端点(每种情况一个端点)要好得多。我只是想知道这种端点的维护性和可扩展性。请查看下面的示例:

@RestController
@RequestMapping("/customers")
public class CustomerController {

    @Autowired
    private CustomerRepository customerRepo;

    @GetMapping
    public Page<Customer> findCustomersByFirstName(
                @RequestParam("firstName") String firstName,
                @RequestParam("lastName") String lastName,
                @RequestParam("status") Status status, Pageable pageable) {

        if (firstName != null) {
            if (lastName != null) {
                if (status != null) {
                    return customerRepo.findByFirstNameAndLastNameAndStatus(
                                                    firstName, lastName, status, pageable);
                } else {
                    return customerRepo.findByFirstNameAndLastName(
                                                    firstName, lastName, pageable);
                }
            } else {
                // other combinations omitted for sanity
            }
        } else {
            // other combinations omitted for sanity
        }
    }
}
@RestController
@请求映射(“/customers”)
公共类客户控制器{
@自动连线
私人客户存储位置客户存储;
@GetMapping
公共页面findCustomersByFirstName(
@RequestParam(“firstName”)字符串firstName,
@RequestParam(“lastName”)字符串lastName,
@RequestParam(“状态”)状态,可分页(可分页){
if(firstName!=null){
if(lastName!=null){
如果(状态!=null){
返回customerRepo.findByFirstNameAndLastNameAndStatus(
名字、姓氏、状态、可分页);
}否则{
返回customerRepo.FindByFirstName和LastName(
名字、姓氏、可分页);
}
}否则{
//为保持理智,省略了其他组合
}
}否则{
//为保持理智,省略了其他组合
}
}
}
这样的端点似乎使用起来非常方便(参数的顺序无关紧要,所有参数都是可选的…),但维护这样的端点看起来像是地狱(组合的数量可能非常多)


我的问题是——处理这种事情的最佳方式是什么?在“专业”API中它是如何设计的?

实际上,您自己回答了一半的答案,查询参数用于过滤目的,正如您在代码中看到的,这将通过GET请求得到允许。但是关于验证的问题是一种权衡

比如,;如果不希望进行此类检查,可以依赖于强制
required=true
,这是
@RequestParam
中的默认值,并立即在响应中处理它

或者,您也可以使用支持@Valid@RequestBody来获得更清晰的错误信息;比如说

@PostMapping(value=“/order”)
public ResponseEntity submitRequest(@RequestBody@Valid OrderRequest RequestBody,
错误)引发异常{
if(errors.hasErrors())
抛出新的BusinessException(“ERR-0000”,HttpStatus.NOT_ACCEPTABLE);
返回新的ResponseEntity(sendOrder(requestBody),HttpStatus.CREATED);
}
//你的Pojo
公共类OrderRequest{
@NotNull(消息=“通道是必需的”)
专用字符串通道;
@NotNull(消息=“需要第三方ID”)
私人长派对;
}
有关更多信息,请查看此

这种方式将使您的验证机制从控制器层到业务层解耦。这反过来将节省大量的样板代码,但正如您所注意到的,更改为POST

因此,一般来说,您的问题没有直接的答案,简短的答案是这取决于您的情况,因此选择任何对您来说简单、功能良好、维护较少的方法将是最佳做法

处理这样的事情最好的方法是什么

处理它的最好方法是使用已有的工具。当您使用SpringBoot时,我假设SpringDataJPA随后为SpringDataJPA启用QueryDsl支持和web支持扩展

然后,您的控制器简单地变成:

@GetMapping
public Page<Customer> searchCustomers( 
        @QuerydslPredicate(root = Customer.class) Predicate predicate, Pageable pageable) {
   return customerRepo.findBy(predicate, pageable);
}
@GetMapping
公众网页搜寻客户(
@QuerydslPredicate(root=Customer.class)谓词,可分页(Pageable){
返回customerRepo.findBy(谓词,可分页);
}
并且您的存储库仅扩展为支持QueryDsl:

public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>, 
            QueryDslPredicateExecutor<Customer>{

}
公共接口CustomerRepository扩展了分页和排序存储库,
QueryDSL谓词执行器{
}
您现在可以通过参数的任意组合进行查询,而无需编写任何进一步的代码。


早安。我不能称自己为专业人士,但这里有一些技巧可以让这个控制器看起来更好

  • 使用DTO而不是使用一组参数
使用此类,方法的签名将如下所示:

@GetMapping
public Page<Customer> findCustomersByFirstName(CustomerDTO customerDTO, Pageable pageable) {
    ...
}
不要忘记在控制器中的DTO参数之前添加@Valid注释

@PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(@Valid @RequestBody Customer customer){
   return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
  • 使用规格,而不是带if-else的此块
这里有一个关于如何做的很好的指南-

  • 使用服务层,不需要从控制器调用存储库
@GetMapping
公共页findCustomersByFirstName(@Valid CustomerDTO CustomerDTO,BindingResult BindingResult,Pageable Pageable){
if(bindingResult.hasErrors()){
//错误处理
}
返回customerService.findAllBySpecification(新CustomerSpecification(customerDTO));
}

您的控制器不应该包含任何与实体或某些业务相关的逻辑。它只涉及处理请求/错误、重定向、视图等。

最好是有一个带有此类验证的POST请求,而不是GET请求。您可以对控制器使用以下方法

@PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(@Valid @RequestBody Customer customer){
   return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
}

并添加控制器级别的异常建议以返回错误响应

@ControllerAdvice
public class ControllerExceptionAdvice {

private static final String EXCEPTION_TRACE = "Exception Trace:";

private static Logger log = LoggerFactory.getLogger(ControllerExceptionAdvice.class);

public ControllerExceptionAdvice() {
    super();
}

@ExceptionHandler({ BaseException.class })
public ResponseEntity<String> handleResourceException(BaseException e, HttpServletRequest request,
                                                      HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(e);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, e.getHttpStatus());
}


@ExceptionHandler({ Exception.class })
public ResponseEntity<String> handleException(Exception e, HttpServletRequest request,
                                              HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.INTERNAL_DEFAULT);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}


@ExceptionHandler({ MethodArgumentNotValidException.class })
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e,
                                                        HttpServletRequest request, HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    ValidationException validationEx = new ValidationException(e);
    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(validationEx);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, validationEx.getHttpStatus());
}


@ExceptionHandler({ HttpMediaTypeNotSupportedException.class, InvalidMimeTypeException.class,
        InvalidMediaTypeException.class, HttpMessageNotReadableException.class })
public ResponseEntity<String> handleMediaTypeNotSupportException(Exception e, HttpServletRequest request,
                                                                 HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.BAD_REQUEST_DEFAULT);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}


@ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public ResponseEntity<String> handleMethodNotSupportException(Exception e, HttpServletRequest request,
                                                              HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.METHOD_NOT_ALLOWED;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.METHOD_NOT_ALLOWED);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}

@ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<String> handleMissingServletRequestParameterException(Exception e, HttpServletRequest request,
                                                                            HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.BAD_REQUEST_DEFAULT);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@ControllerAdvice
公共类控制器异常通知{
私有静态最终字符串异常\u TRACE=“异常跟踪:”;
私有静态记录器log=LoggerFactory.getLogger(ControllerExceptionAdvice.class);
公共控制例外广告(){
超级();
}
@ExceptionHandler({BaseException.class})
公共响应处理人员
@PostMapping(value = "/findCustomer",produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> findCustomersByFirstName(@Valid @RequestBody Customer customer){
   return customerRepo.findByFirstNameAndLastNameAndStatus(customer.getFirstName, customer.getLastName(), customer.getStatus(), pageable);
public class Customer {

private String firstName;
private String lastName;
private String status;

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName= firstName;
}

public String getLastName() {
    return lastName;
}

public void setLastName(String lastName) {
    this.lastName= lastName;
}

public String getStatus() {
    return status;
}

public void setStatus(String status) {
    this.status= status;
}

public LivenessInputModel(String firstName, String lastName, String status) {
    this.firstName= firstName;
    this.lastName= lastName;
    this.status= status;
}

public LivenessInputModel() {

}
@ControllerAdvice
public class ControllerExceptionAdvice {

private static final String EXCEPTION_TRACE = "Exception Trace:";

private static Logger log = LoggerFactory.getLogger(ControllerExceptionAdvice.class);

public ControllerExceptionAdvice() {
    super();
}

@ExceptionHandler({ BaseException.class })
public ResponseEntity<String> handleResourceException(BaseException e, HttpServletRequest request,
                                                      HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(e);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, e.getHttpStatus());
}


@ExceptionHandler({ Exception.class })
public ResponseEntity<String> handleException(Exception e, HttpServletRequest request,
                                              HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.INTERNAL_DEFAULT);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}


@ExceptionHandler({ MethodArgumentNotValidException.class })
public ResponseEntity<String> handleValidationException(MethodArgumentNotValidException e,
                                                        HttpServletRequest request, HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    ValidationException validationEx = new ValidationException(e);
    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(validationEx);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, validationEx.getHttpStatus());
}


@ExceptionHandler({ HttpMediaTypeNotSupportedException.class, InvalidMimeTypeException.class,
        InvalidMediaTypeException.class, HttpMessageNotReadableException.class })
public ResponseEntity<String> handleMediaTypeNotSupportException(Exception e, HttpServletRequest request,
                                                                 HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.BAD_REQUEST_DEFAULT);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}


@ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
public ResponseEntity<String> handleMethodNotSupportException(Exception e, HttpServletRequest request,
                                                              HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.METHOD_NOT_ALLOWED;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.METHOD_NOT_ALLOWED);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}

@ExceptionHandler({ MissingServletRequestParameterException.class })
public ResponseEntity<String> handleMissingServletRequestParameterException(Exception e, HttpServletRequest request,
                                                                            HttpServletResponse response) {

    log.error(EXCEPTION_TRACE, e);

    HttpHeaders responseHeaders = new HttpHeaders();

    responseHeaders.setContentType(MediaType.APPLICATION_JSON);

    HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

    BaseExceptionResponse exceptionDto = new BaseExceptionResponse(httpStatus.value(),
            ExceptionMessages.BAD_REQUEST_DEFAULT);

    return new ResponseEntity<>(exceptionDto.toString(), responseHeaders, httpStatus);
}
@Repository
interface CustomerRepository extends JpaSpecificationExecutor<Customer> {

}
@RestController
@RequestMapping("/customers")
public class CustomerController {
    private final CustomerRepository repository;

    @Autowired
    public CustomerController(CustomerRepository repository) {
        this.repository = repository;
    }

    @GetMapping
    public Page<Customer> findAll(@RequestBody HashMap<String, String> filters, Pageable pageable) {
        return repository.findAll(QueryUtils.toSpecification(filters), pageable);
    }
}
class QueryUtils {
    public static Specification<Customer> toSpecification(Map<String, String> filters) {
        Specification<Customer> conditions = null;

        for (Map.Entry<String, String> entry : filters.entrySet()) {
            Specification<Customer> condition = Specification.where((root, query, cb) -> cb.equal(root.get(entry.getKey()), entry.getValue()));
            if (conditions == null) {
                conditions = condition;
            } else {
                conditions = conditions.and(condition);
            }
        }

        return conditions;
    }
}