做一个优雅的接口

前言

项目开发中难免会碰见各种非空校验,比如用户登录用户注册等。
通常的操作再接收处理的接口中,添加各种if ... if ..判断,不够优雅,如何才能做一个简洁易懂的接口呢?

环境搭建

本次测试使用的是零配置Springboot项目。
依赖引入:

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.14.RELEASE</version>
	</parent>

	<properties>
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		
		<!-- 阿里巴巴json -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.44</version>
		</dependency>
		<!-- lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.6</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

配置文件编写:

server.port=80

定义一个数据接受类User

@Data
public class User implements Serializable {
	
	private static final long serialVersionUID = -2460669663541910243L;

	@NotNull(message = "用户id不能为空")
	private Integer id;
	
	@NotNull(message = "账号不允许为空")
	@Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
	private String account;
	
	@NotNull(message = "密码不允许为空")
	@Size(min = 6, max = 11, message = "密码长度必须是6-16个字符")
	private String password;
}

定义一个拦截器TestController

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.vo.User;

@RestController
public class TestController {
	
	private Logger log = LoggerFactory.getLogger(TestController.class);
	
	@RequestMapping(value = "/addUser",method = RequestMethod.POST)
	public String addUser(@RequestBody @Valid User user, 
			BindingResult bindingResult) {
		// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
        for (ObjectError error : bindingResult.getAllErrors()) {
        	log.info("--->"+error.getDefaultMessage());
            return error.getDefaultMessage();
        }
		return "6666";
	}
}

使用PostMan发送一个指定的post请求测试:

localhost/addUser
在这里插入图片描述

获取到的结果返回为:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

这样再接口请求时(@RequestBody标识为一个json对象),就通过(@Valid注解标识)javax.validation 实现了基本的数据校验操作。
如果出现不符合规范的错误信息(@NotNull、@Size返回的message信息),则会将信息保存至org.springframework.validation.BindingResult对象中返回。

但是,是否想过一个问题:

每次写一个新的接口,都需要给接口中接受对象处标注@Valid,同时增加一个BindingResult类做错误信息返回,过于累赘!

如何才能尽可能将接口开发显得更加简洁优雅美观些呢?

去掉BindingResult

再平时开发过程中,出现异常时,程序不会继续进行下去,同时也能将异常报错信息返回至请求的客户端。

参照这个思路,可以再将代码进行小小的优化。

去掉 BindingResult对象接受错误信息,请求接口测试:

@RequestMapping(value = "/addUser1",method = RequestMethod.POST)
	public String addUser1(@RequestBody @Valid User user) {
        
		return "6666";
	}

在这里插入图片描述

{
    "timestamp": "2020-07-21T08:27:17.793+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Size.user.account",
                "Size.account",
                "Size.java.lang.String",
                "Size"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.account",
                        "account"
                    ],
                    "arguments": null,
                    "defaultMessage": "account",
                    "code": "account"
                },
                11,
                6
            ],
            "defaultMessage": "账号长度必须是6-11个字符",
            "objectName": "user",
            "field": "account",
            "rejectedValue": "12345",
            "bindingFailure": false,
            "code": "Size"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 1",
    "path": "/addUser1"
}

并且再控制台也出现了一个错误信息提示:

Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.lang.String com.example.controller.TestController.addUser1(com.example.vo.User): [Field error in object ‘user’ on field ‘account’: rejected value [12345]; codes [Size.user.account,Size.account,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.account,account]; arguments []; default message [account],11,6]; default message [账号长度必须是6-11个字符]] ]

既然是一个异常,参照之前的想法,大胆的构思一种新的方式———全局异常接收回执信息。

使用全局异常

定义一个 RestControllerAdvice(或 ControllerAdvice) 类,对指定的异常信息进行全局监听处理。

import java.util.List;

import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * spring 3.2中,新增了@ControllerAdvice 注解,<br>
 * 可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,<br>
 * 并应用到所有@RequestMapping中。
 * @author 765199214
 *
 */
@RestControllerAdvice 
//@ControllerAdvice //根据需要监测的controller上的注解区分使用
public class ExceptionAdvice {
	
	/**
	 * 针对  MethodArgumentNotValidException 异常,做全局监听处理
	 * @param me
	 * @return
	 */
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
	public String MethodArgumentNotValidException(MethodArgumentNotValidException me) {
		BindingResult bindingResult = me.getBindingResult();
		List<ObjectError> allErrors = bindingResult.getAllErrors();
		ObjectError objectError = allErrors.get(0);
		return objectError.getDefaultMessage();
	}
}

再次请求 /addUser1 接口测试:
在这里插入图片描述
在这里插入图片描述

合适的接口规范

现在的开发,通常采取json风格,携带状态码code、状态信息msg、结果信息data。

比如:参数不符合规范,返回code 10000,msg为提示信息等,修改全局异常返回数据结构。

	/**
	 * 针对  MethodArgumentNotValidException 异常,做全局监听处理
	 * @param me
	 * @return
	 */
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
	public JSONObject MethodArgumentNotValidException(MethodArgumentNotValidException me) {
		log.info("--->com.example.ex.ExceptionAdvice.MethodArgumentNotValidException");
		BindingResult bindingResult = me.getBindingResult();
		List<ObjectError> allErrors = bindingResult.getAllErrors();
		ObjectError objectError = allErrors.get(0);
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("code", 10000);
		jsonObject.put("msg", objectError.getDefaultMessage());
		jsonObject.put("data", "");
		return jsonObject;
	}

在这里插入图片描述
这样的操作,很简洁明了,也适合前后分离等项目的开发。

国际化规范

上面的案例操作,依旧欠缺开发规范,每种参数的校验提示参数都写在每个对应的类中,并不利于后期的维护更改操作,如何更加优雅的做操作呢?

src/main/resources包中创建一个新的包文件il8n。并新增一个Messages.properties文件。其中内容为:

test.msg.id=用户id不能为空
test.msg.account=账号不允许为空
test.msg.account.size=账号长度必须是6-11个字符
test.msg.password=密码不允许为空
test.msg.password.size=密码长度必须是6-16个字符

在配置文件中增加扫描配置操作:

server.port=80

 # 多个文件时,采取“,”拼接
spring.messages.basename=il8n/Messages 

重新定义一个新的操作对象类 TestVo,修改其中的message信息。

import java.io.Serializable;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class TestVo implements Serializable {

	private static final long serialVersionUID = 7563633519927149180L;
	
	@NotNull(message = "test.msg.id")
	private Integer id;
	
	@NotNull(message = "test.msg.account")
	@Size(min = 6, max = 11, message = "test.msg.account.size")
	private String account;
	
	@NotNull(message = "test.msg.password")
	@Size(min = 6, max = 11, message = "test.msg.password.size")
	private String password;
}

新建一个读取message文件的抽象类AbstractBaseController

import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;

public abstract class AbstractBaseController {
	
	@Autowired
	private MessageSource messageSource;
	
	public String getMessage(String key,String... strings ) {
		return this.messageSource.getMessage(key, strings, Locale.getDefault());
	}
}

修改全局异常监听类,采取获取message的状态码的方式,读取指定配置文件获取对应的提示信息:

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.alibaba.fastjson.JSONObject;
import com.example.enums.CallBackEnum;
import com.example.vo.ResultVO;

/**
 * spring 3.2中,新增了@ControllerAdvice 注解,<br>
 * 可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,<br>
 * 并应用到所有@RequestMapping中。
 * @author 765199214
 *
 */
@RestControllerAdvice 
//@ControllerAdvice //根据需要监测的controller上的注解区分使用
public class ExceptionAdvice extends AbstractBaseController {
	
	private Logger log = LoggerFactory.getLogger(ExceptionAdvice.class);

	/**
	 * 针对  MethodArgumentNotValidException 异常,做全局监听处理
	 * @param me
	 * @return
	 */
	@ExceptionHandler(value = MethodArgumentNotValidException.class)
	public JSONObject MethodArgumentNotValidException(MethodArgumentNotValidException me) {
		log.info("--->com.example.ex.ExceptionAdvice.MethodArgumentNotValidException");
		BindingResult bindingResult = me.getBindingResult();
		List<ObjectError> allErrors = bindingResult.getAllErrors();
		ObjectError objectError = allErrors.get(0);
		String key = objectError.getDefaultMessage();
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("code", 10000);
		jsonObject.put("msg", super.getMessage(key, null));
		jsonObject.put("data", "");
		return jsonObject;
	}
}

新建一个测试接口:

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.vo.TestVo;
import com.example.vo.User;

@RestController
public class TestController {
	
	private Logger log = LoggerFactory.getLogger(TestController.class);
	
	@RequestMapping(value = "/test",method = RequestMethod.POST)
	public String test(@RequestBody @Valid TestVo testVo) {
        
		return "6666";
	}
}

请求测试接口:
在这里插入图片描述


文章参考资料:
用SpringBoot手把手教你写出优雅的后端接口
@RestControllerAdvice作用及原理
@Valid注解是什么


文章代码下载:
github下载地址

猜你喜欢

转载自blog.csdn.net/qq_38322527/article/details/107491494