当前位置:网站首页>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 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 .
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 !
边栏推荐
- 每周推荐短视频:L2级有哪些我们日常中经常会用到的功能?
- 0x0fa23729 (vcruntime140d.dll) (in classes and objects - encapsulation.Exe) exception thrown (resolved)
- 基于gis三维可视化技术的智慧城市建设
- Guid主键
- leetcode-304:二维区域和检索 - 矩阵不可变
- ORM model -- creation and query of data records
- Slurm资源管理与作业调度系统安装配置
- Postman interface test IV
- Factorial implementation of large integer classes
- HDU-2196 树形DP学习笔记
猜你喜欢
STM32中AHB总线_APB2总线_APB1总线这些是什么
Talking about the return format in the log, encapsulation format handling, exception handling
一文讲解单片机、ARM、MUC、DSP、FPGA、嵌入式错综复杂的关系
[second on] [jeecgboot] modify paging parameters
使用U2-Net深层网络实现——证件照生成程序
Chris Lattner, père de llvm: Pourquoi reconstruire le logiciel d'infrastructure ai
深入分析ERC-4907协议的主要内容,思考此协议对NFT市场流动性意义!
对word2vec的一些浅层理解
Guide de signature du Code Appx
基于HPC场景的集群任务调度系统LSF/SGE/Slurm/PBS
随机推荐
Experience sharing of software designers preparing for exams
Study summary of postgraduate entrance examination in October
Easyexcel read write simple to use
mysql插入数据创建触发器填充uuid字段值
移动端通过设置rem使页面内容及字体大小自动调整
深入分析ERC-4907协议的主要内容,思考此协议对NFT市场流动性意义!
The width of table is 4PX larger than that of tbody
Study summary of postgraduate entrance examination in September
Appx code signing Guide
1324:【例6.6】整数区间
PDF文档签名指南
The mobile terminal automatically adjusts the page content and font size by setting rem
Chris Lattner, père de llvm: Pourquoi reconstruire le logiciel d'infrastructure ai
Leetcode exercise - 113 Path sum II
C#记录日志方法
leetcode-560:和为 K 的子数组
AHB bus in stm32_ Apb2 bus_ Apb1 bus what are these
JMeter installation
STM32 Basics - memory mapping
2022.7.5DAY597