当前位置:网站首页>Elegant controller layer code

Elegant controller layer code

2022-07-07 10:27:00 IT Wolf

A complete back-end request is made by 4 Part of it is made up of :

  • Address of the interface ( That is to say URL Address )

  • Request mode ( It is commonly get、set, Of course, put、delete)

  • Request data (request, Yes head Follow body)

  • The response data (response)

Currently solve the following 3 A question :

  • When a request is received , How to check parameters gracefully

  • How to uniformly process the return response data

  • Received request , How to handle exceptions thrown when processing business logic

 

1、Controller Layer parameter receiving

Based on the , Skippable ...

Common requests are divided into get Follow post Two kinds of :

@[email protected]("/product/product-info")public class ProductInfoController {

    @Autowired    ProductInfoService productInfoService;

    @GetMapping("/findById")    public ProductInfoQueryVo findById(Integer id) {
        ...
    }

    @PostMapping("/page")    public IPage findPage(Page page, ProductInfoQueryVo vo) {
        ...
    }
}

 

  • RestController:

    I've explained before ,@[email protected]+ResponseBody.

    Add this note ,springboot I will treat this class as controller To deal with , Then put all the returned parameters into ResponseBody in .

  • @RequestMapping:

    Prefix requested , That is all that should be Controller All requests under the need to add /product/product-info The prefix of .

  • @GetMapping("/findById"):

    Mark this is a get request , And need to pass /findById Address to access .

  • @PostMapping("/page"):

    Empathy , It means it's a post request .

    Parameters : As for the parameters , Just write ProductInfoQueryVo, From the front json The request will be assigned to the corresponding object by mapping , For example, ask to write ,productId Will be automatically mapped to vo Corresponding attributes .

 

size : 1current : 1productId : 1productName :  Soak the foot 

 

2、 Unified status code

Returns the format

In order to have a good relationship with the front-end sister , We usually need to wrap the data returned from the back end , Add a status code , State information , In this way, the front-end sister can receive data according to different status codes , Determine the response data status , Whether it is successful or abnormal is displayed differently .

Of course, this gives you more opportunities to communicate with your front-end sister , Suppose we make an agreement 1000 It means success .

If you don't package , Then the returned data looks like this :

{
  "productId": 1,
  "productName": " Soak the foot ",
  "productPrice": 100.00,
  "productDescription": " Chinese medicine feet soaking and massage ",
  "productStatus": 0,
}

It looks like this after packaging :

{
  "code": 1000,
  "msg": " The request is successful ",
  "data": {
    "productId": 1,
    "productName": " Soak the foot ",
    "productPrice": 100.00,
    "productDescription": " Chinese medicine feet soaking and massage ",
    "productStatus": 0,
  }
}

 

encapsulation ResultVo

These status codes must be prepared in advance , How to make it up ? Write a constant 1000? Or just write it dead 1000?

If you want to write in this way, you will really read the book for nothing , Writing the status code is, of course, an enumeration :

  1. First, define a status code interface , All status codes need to implement it , Only with standards can we do things :

public interface StatusCode {
    public int getCode();
    public String getMsg();
}

      2. Then go to the front sister , Make an appointment with him for the status code ( This may be your only Agreement ) Enumerating classes , Of course not setter The method , So we can't use @Data Note the , We need to use it. @Getter.

@Getterpublic enum ResultCode implements StatusCode{
    SUCCESS(1000, " The request is successful "),
    FAILED(1001, " request was aborted "),
    VALIDATE_ERROR(1002, " Parameter verification failed "),
    RESPONSE_PACK_ERROR(1003, "response Return packaging failure ");

    private int code;
    private String msg;

    ResultCode(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

        3. Write enumeration classes , Start writing ResultVo Packaging class , We preset several default methods , For example, if it succeeds, it will be passed in by default object That's all right. , We automatically pack it into success.

@Datapublic class ResultVo {
    //  Status code     private int code;

    //  State information     private String msg;

    //  Returns the object     private Object data;

    //  Manually set the return vo    public ResultVo(int code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    //  The success status code is returned by default , Data objects     public ResultVo(Object data) {
        this.code = ResultCode.SUCCESS.getCode();
        this.msg = ResultCode.SUCCESS.getMsg();
        this.data = data;
    }

    //  Returns the specified status code , Data objects     public ResultVo(StatusCode statusCode, Object data) {
        this.code = statusCode.getCode();
        this.msg = statusCode.getMsg();
        this.data = data;
    }

    //  Only the status code is returned     public ResultVo(StatusCode statusCode) {
        this.code = statusCode.getCode();
        this.msg = statusCode.getMsg();
        this.data = null;
    }
}

       4. Use , Now the return is definitely not return data; It's so simple , But needs new ResultVo(data);

@PostMapping("/findByVo")public ResultVo findByVo(@Validated ProductInfoVo vo) {
    ProductInfo productInfo = new ProductInfo();
    BeanUtils.copyProperties(vo, productInfo);
    return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}

Finally, the data with the status code will be returned .

 

 

3、 Unified verification

The original way

Suppose you have an add ProductInfo The interface of , When there is no unified verification , We need to do this .

@Datapublic class ProductInfoVo {
    //  Name of commodity     private String productName;
    //  commodity price     private BigDecimal productPrice;
    //  On the shelf status     private Integer productStatus;
}
@PostMapping("/findByVo")public ProductInfo findByVo(ProductInfoVo vo) {
    if (StringUtils.isNotBlank(vo.getProductName())) {
        throw new APIException(" Commodity name cannot be empty ");
    }
    if (null != vo.getProductPrice() && vo.getProductPrice().compareTo(new BigDecimal(0)) < 0) {
        throw new APIException(" Commodity prices cannot be negative ");
    }
    ...

    ProductInfo productInfo = new ProductInfo();
    BeanUtils.copyProperties(vo, productInfo);
    return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}

this if People who write are stupid , Can you bear it ? I can't bear it .

@Validated Parameter checking

Fortunately, there are @Validated, It is also a necessary medicine for checking parameters . With @Validated We just need to vo Add a little note on it , The verification function can be completed .

@Datapublic class ProductInfoVo {
    @NotNull(message = " The product name cannot be empty ")    private String productName;

    @Min(value = 0, message = " The commodity price cannot be negative ")    private BigDecimal productPrice;

    private Integer productStatus;
}
@PostMapping("/findByVo")public ProductInfo findByVo(@Validated ProductInfoVo vo) {
    ProductInfo productInfo = new ProductInfo();
    BeanUtils.copyProperties(vo, productInfo);
    return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));
}

Run it , What happens if the parameters are wrong ?

We deliberately sent a price for -1 The parameters of the past :

productName :  Soak the foot 
productPrice : -1productStatus : 1
{
  "timestamp": "2020-04-19T03:06:37.268+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "Min.productInfoVo.productPrice",
        "Min.productPrice",
        "Min.java.math.BigDecimal",
        "Min"      ],
      "arguments": [
        {
          "codes": [
            "productInfoVo.productPrice",
            "productPrice"          ],
          "defaultMessage": "productPrice",
          "code": "productPrice"        },
        0      ],
      "defaultMessage": " The commodity price cannot be negative ",
      "objectName": "productInfoVo",
      "field": "productPrice",
      "rejectedValue": -1,
      "bindingFailure": false,
      "code": "Min"    }
  ],
  "message": "Validation failed for object\u003d\u0027productInfoVo\u0027. Error count: 1",
  "trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: java.base/java.lang.Thread.run(Thread.java:830)\n",
  "path": "/leilema/product/product-info/findByVo"}

Is it a success ? Although the parameters were successfully verified , An exception is also returned , And bring them up. " The commodity price cannot be negative " Information about .

But if you return to the front end like this , The front sister came over with a knife , The status code agreed in that year , You are a heartless person. You forget when you say you forget ?

The user experience is less than or equal to 0 ah ! So we need to optimize , Every time something goes wrong , Write the status code automatically , Live up to your sister's promise !

Optimize exception handling

First, let's see what exceptions are thrown by the validation parameters :

Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors

We see that the code throws org.springframework.validation.BindException Binding exception for , So our idea is AOP Intercept all controller, Then the exceptions are intercepted uniformly , encapsulate !perfect!

 

Where perfect , This operation springboot Don't you know ?spring mvc Of course I know , So it provides us with a @RestControllerAdvice To enhance all @RestController, And then use @ExceptionHandler annotation , You can intercept the corresponding exception .

Here we intercept BindException.class Just fine . Finally, before returning , Let's wrap the exception information , Package as ResultVo, Of course, keep up ResultCode.VALIDATE_ERROR Exception status code of .

So the front-end sister sees VALIDATE_ERROR The status code , The pop-up window of data verification exception will be called to prompt the user where the data has not been filled .

@RestControllerAdvicepublic class ControllerExceptionAdvice {

    @ExceptionHandler({BindException.class})     public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
        //  Get... From the exception object ObjectError object         ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
    }
}

Let's see the effect , perfect .1002 The status code agreed with the front-end sister :

{
  "code": 1002,
  "msg": " Parameter verification failed ",
  "data": " The commodity price cannot be negative "}

4、 Unified response

Unified packaging response

Look back controller Layer return :

return new ResultVo(productInfoService.getOne(new QueryWrapper(productInfo)));

I'm not happy about it , Who has time to write every day new ResultVo(data) ah , I just want to return an entity ! I don't care how to achieve it !

All right. , That's it AOP Intercept all Controller, Again @After I will help you to package it when you are ready .

3628811953d2f4f2478300cc3f94769b.png

I'm afraid it didn't hurt enough last time ,springboot Don't you know such an operation ?

@RestControllerAdvice(basePackages = {"com.bugpool.leilema"})public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        // response yes ResultVo type , Or annotated NotControllerResponseAdvice No packaging         return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class);
    }

    @Override    public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) {
        // String Type cannot be packaged directly         if (returnType.getGenericParameterType().equals(String.class)) {
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                //  Package data in ResultVo Convert from inside to back json String to return                 return objectMapper.writeValueAsString(new ResultVo(data));
            } catch (JsonProcessingException e) {
                throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
            }
        }
        //  Otherwise, it is directly packaged into ResultVo return         return new ResultVo(data);
    }
}
  1. @RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) Automatically scan all the specified packages controller, stay Response Unified processing shall be carried out when .

  2. rewrite supports Method , in other words , When the return type is already ResultVo 了 , Then there is no need to encapsulate , When not equal to ResultVo Only when beforeBodyWrite Method , The effect is the same as that of the filter .

  3. Finally, rewrite our encapsulation method beforeBodyWrite, Note that except for String The return value of is a bit special , Cannot be encapsulated directly into json, We need to do something special , Other direct new ResultVo(data); Just ok 了 .

Finish off work , Look at the effect :

@PostMapping("/findByVo")public ProductInfo findByVo(@Validated ProductInfoVo vo) {
    ProductInfo productInfo = new ProductInfo();
    BeanUtils.copyProperties(vo, productInfo);
    return productInfoService.getOne(new QueryWrapper(productInfo));
}

At this point, even if we return to po, The received return is in the standard format , Brother Kaifa showed a happy smile .

{
  "code": 1000,
  "msg": " The request is successful ",
  "data": {
    "productId": 1,
    "productName": " Soak the foot ",
    "productPrice": 100.00,
    "productDescription": " Chinese medicine feet soaking and massage ",
    "productStatus": 0,
    ...
  }
}

NOT Unified response

** Reason for not opening unified response :** Brother Kaifa is happy , But other systems are not happy . for instance : A health detection function is integrated in our project , That is, the goods .

@RestControllerpublic class HealthController {
    @GetMapping("/health")    public String health() {
        return "success";
    }
}

The company has deployed a set of tools to verify the survival status of all systems , This tool sends messages regularly get Request to our system :

“ brother , Are you dead ?” “ I'm not dead , roll ” “ brother , Are you dead ?” “ I'm not dead , roll ”

Yes ,web The essence of the project is the repeater . Once the sent request is not responded , Will send a message to the person in charge ( Enterprise wechat or SMS ), Your system is dead ! Hurry back to check bug Well !

Just to give you a sense . Every time I see me, I shake , morning 6 spot ! I tm!!!!!

3bb91fe2b91ed775b55dd72951d0f608.png

ok , Can't , He's the boss , What people want to return is not :

{
  "code": 1000,
  "msg": " The request is successful ",
  "data": "success"}

Only one return is required success, The standards set by others cannot be changed because of your system . As the saying goes , If you can't change the environment , Then you can only let me ****

** Add no encapsulation annotation :** Because percent 99 Your request still needs packaging , Only a few don't need , Write the filter on the package ? It is not very easy to maintain , Then add a note . Add this note to all that do not need packaging .

@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface NotControllerResponseAdvice {
}

Then filter the method containing this annotation on our enhanced filtering method :

@RestControllerAdvice(basePackages = {"com.bugpool.leilema"})public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> {
    @Override    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        // response yes ResultVo type , Or annotated NotControllerResponseAdvice No packaging         return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)                 || methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class));
    }
    ...

Finally, add notes on the methods that do not need packaging :

@RestControllerpublic class HealthController {

    @GetMapping("/health")    @NotControllerResponseAdvice    public String health() {
        return "success";
    }
}

At this time, it will not be automatically encapsulated , Others without comments are still automatically packaged :

4264378f41bc6813214ea28b2ac9d25d.png

5、 Unification exception

Each system has its own business exceptions , For example, the inventory cannot be less than 0 A subclass , This exception is not a program exception , It is an exception caused by a business operation , We also need to arrange the business exception status code , And write a special exception class , Finally, the exception interception just learned is handled uniformly , And log .

  1. Exception status code enumeration , Since it is a status code , Then we must implement our standard interface StatusCode.

    @Getterpublic enum  AppCode implements StatusCode {
    
        APP_ERROR(2000, " Business exceptions "),
        PRICE_ERROR(2001, " The price is abnormal ");
    
        private int code;
        private String msg;
    
        AppCode(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }
    }
  2. Exception class , It needs to be emphasized here ,code representative AppCode Exception status code of , That is to say 2000;msg Represents business exceptions , This is just a big category , Generally, the front end will be placed in the pop-up window title On ; Last super(message); This is the detail of the throw , Display in pop-up window at the front end , stay ResultVo Keep it in data in .

    @Getterpublic class APIException extends RuntimeException {
        private int code;
        private String msg;
    
        //  Manual setting exception     public APIException(StatusCode statusCode, String message) {
            // message Used to set the details of the error thrown by the user , for example : Current price -5, Less than 0        super(message);
            //  Status code         this.code = statusCode.getCode();
            //  Status code matching msg        this.msg = statusCode.getMsg();
        }
    
        //  Default exception uses APP_ERROR Status code     public APIException(String message) {
            super(message);
            this.code = AppCode.APP_ERROR.getCode();
            this.msg = AppCode.APP_ERROR.getMsg();
        }
    
    }
  3. Finally, intercept the unified exception , So no matter in service Layer or controller layer , Developers just throw API abnormal , There is no need to return the relationship to the front end , There is no need to care about the printing of logs .

    @RestControllerAdvicepublic class ControllerExceptionAdvice {
    
        @ExceptionHandler({BindException.class})     public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
            //  Get... From the exception object ObjectError object         ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
            return new ResultVo(ResultCode.VALIDATE_ERROR, objectError.getDefaultMessage());
        }
    
        @ExceptionHandler(APIException.class)     public ResultVo APIExceptionHandler(APIException e) {
            // log.error(e.getMessage(), e);  Since the logging framework has not been integrated yet , For the time being , write TODO        return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
        }
    }
  4. Finally using , Our code just needs to be written like this .

    if (null == orderMaster) {
       throw new APIException(AppCode.ORDER_NOT_EXIST, " The order number does not exist :" + orderId);
    }

    effect :

    {
      "code": 2003,
      "msg": " The order does not exist ",
      "data": " The order number does not exist :1998"}

It will automatically throw out AppCode.ORDER_NOT_EXIST State code response , And the order number with exception details does not exist :xxxx.

The development efficiency of the back-end brother , Front end sister gets 2003 Status code , Call the corresponding warning pop-up ,title Write that the order does not exist ,body Detailed information records " The order number does not exist :1998". At the same time, the log is automatically printed !

 

 

 

 

 

 

 

原网站

版权声明
本文为[IT Wolf]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/07/202207070818554015.html