当前位置:网站首页>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 :
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 errorsWe 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 .

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);
}
}@RestControllerAdvice(basePackages = {"com.bugpool.leilema"}) Automatically scan all the specified packages controller, stay Response Unified processing shall be carried out when .
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 .
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!!!!!

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 :

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 .
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; } }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(); } }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()); } }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 !
边栏推荐
猜你喜欢

原型与原型链

一文讲解单片机、ARM、MUC、DSP、FPGA、嵌入式错综复杂的关系

Guide de signature du Code Appx

fiddler-AutoResponder

Chris Lattner, père de llvm: Pourquoi reconstruire le logiciel d'infrastructure ai

01 use function to approximate cosine function (15 points)

Postman interface test VI

Encrypt and decrypt stored procedures (SQL 2008/sql 2012)

OpenGL glLightfv 函数的应用以及光源的相关知识

STM32 Basics - memory mapping
随机推荐
leetcode-560:和为 K 的子数组
Postman interface test IV
ORM -- grouping query, aggregation query, query set queryset object properties
Appx代碼簽名指南
使用U2-Net深层网络实现——证件照生成程序
对word2vec的一些浅层理解
The story of Plato and his three disciples: how to find happiness? How to find the ideal partner?
IPv4套接字地址结构
基于gis三维可视化技术的智慧城市建设
Socket通信原理和实践
学习记录——高精度加法和乘法
ORM model -- creation and query of data records
5个chrome简单实用的日常开发功能详解,赶快解锁让你提升更多效率!
Easyexcel read write simple to use
Serial communication relay Modbus communication host computer debugging software tool project development case
Postman interface test III
STM32 ADC和DMA
SQLyog数据库怎么取消自动保存更改
Guide de signature du Code Appx
大整数类实现阶乘