学而时习之,不亦说乎
对于一个由后端提供的接口来说,有一个统一的响应格式,方便入参校验,统一的异常处理,是必不可少的,今天我们将这三个基础功能集成到项目中,使项目更贴近实际的开发场景。
在项目开发中,一般返回给前端的都会是一个统一的返回响应对象,因此后端需要封装一个泛型类来作为响应对象,这样做的好处是前后端能统一接口返回,可以做规范的响应处理。
实现步骤:
package com.mingx.common;
public class AppResult<T> {
private int code;
private String msg;
private T data;// 数据
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
package com.mingx.common;
public class AppResultBuilder {
private static Integer successCode = ResultCode.SUCCESS.getCode();
private static String successMsg = ResultCode.SUCCESS.getMsg();
//成功,不返回具体数据
public static AppResult<String> success(){
AppResult<String> result = new AppResult<String>();
result.setCode(successCode);
result.setMsg(successMsg);
result.setData("");
return result;
}
//成功,返回数据
public static <T> AppResult<T> success(T t){
AppResult<T> result = new AppResult<T>();
result.setCode(successCode);
result.setMsg(successMsg);
result.setData(t);
return result;
}
//失败,返回失败信息
public static <T> AppResult<T> error(ResultCode code){
AppResult<T> result = new AppResult<T>();
result.setCode(code.getCode());
result.setMsg(code.getMsg());
return result;
}
//失败,返回失败信息
public static <T> AppResult<T> error(ResultCode code,String extraMsg){
AppResult<T> result = new AppResult<T>();
result.setCode(code.getCode());
result.setMsg(code.getMsg() + "," + extraMsg);
return result;
}
//失败,返回失败信息
public static <T> AppResult<T> error(Integer code,String extraMsg){
AppResult<T> result = new AppResult<T>();
result.setCode(code);
result.setMsg(extraMsg);
return result;
}
}
package com.mingx.common;
public enum ResultCode {
/* 成功状态码 */
SUCCESS(10000, "success"),
/* 系统错误: */
SYSTEM_ERROR(10001, "系统繁忙,请稍后重试"),
/* 参数错误: */
PARAM_ERROR(10002, "参数有误"),
/* 非法登录:*/
ILLEGAL_ERROR(10003, "用户非法登录"),
/* 用户模块:20001-29999*/
USER_NOT_LOGGED_IN(20001, "用户未登录"),
USER_LOGIN_ERROR(20002, "用户名或者密码错误,请检查重试"),
USER_ACCOUNT_FORBIDDEN(20003, "账号已被禁用"),
USER_HAS_EXISTED(20004, "用户已存在");
private Integer code;
private String msg;
ResultCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
平时项目中,难免需要对参数 进行一些参数正确性的校验,这些校验出现在业务代码中,让我们的业务代码显得臃肿,而且,频繁的编写这类参数校验代码很无聊。鉴于此,觉得 Hibernate Validator 框架刚好解决了这些问题,可以很优雅的方式实现参数的校验,让业务代码 和 校验逻辑 分开,不再编写重复的校验逻辑。
Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
Bean Validation 为 JavaBean 验证定义了相应的元数据模型和API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
准备步骤:
<hibernate-validator.version>6.0.14.Final</hibernate-validator.version>
<!-- hibernate validator主版本管理 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
package com.mingx.common;
/**
* 通用正则表达式
*
* @author Admin
*/
public interface RegexpContants {
/**
* 可空标记
*/
String NULLFLAG = "^$|";
/**
* 手机正则表达式
*/
String MOBIL_EREGEXP = "^(13|14|15|16|17|18|19)\\d{9}$";
/**
* 邮箱正则表达式
*/
String EMAIL_EREGEXP = "^\\s*\\w+(?:\\.{0,1}[\\w-]+)*@[a-zA-Z0-9]+(?:[-.][a-zA-Z0-9]+)*\\.[a-zA-Z]+\\s*$";
/**
* 身份证正则表达式
*/
String ID_CARD_EREGEXP = "(^\\d{15}$)|(^\\d{18}$)|(^\\d{17}(\\d|X|x)$)";
/**
* 固话正则表达式
*/
String TELEPHONE_EREGEXP = "^(0\\d{2,3}-)?\\d{7,8}(-\\d{3,4})?$";
/**
* 网站正则表达式
*/
String URL_EREGEXP = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
/**
* 6位短信验证码
*/
String SIXNUMBER_EREGEXP = "^\\d{6}$";
/**
* 4位短信验证码
*/
String FOURNUMBER_EREGEXP = "^\\d{4}$";
/**
* 验证数字
*/
String NUMBER_EREGEXP = "^\\d{1,}$";
/**
* 年龄
*/
String AGE_EREGEXP = "^[0-9]{1,2}$";
/**
* 密码(6-12位字母或数字)正则表达式
*/
String PASSWORD_OR_EREGEXP = "^[0-9A-Za-z]{6,12}$";
/**
* 密码(6-12位字母和数字)正则表达式
*/
String PASSWORD_AND_EREGEXP = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,12}$";
/**
* 中文姓名正则表达式
*/
String CHINESE_NAME_EREGEXP = "^[\u4e00-\u9fa5]+(\\·[\u4e00-\u9fa5]+)*$";
/**
* 金额正则表达式 正整数,不能为小数或者负数
*/
String MONEY_EREGEXP = "^([1-9]\\d*)*$";
/**
* 只能输入数字 0 或 1
**/
String ZERO_OR_ONE_EREGEXP = "^[0-1]{1}$";
/**
* 正整数
*/
String POSITIVE_NUMBER = "^[0-9]*[1-9][0-9]*$";
/**
* 年月日日期模式
*/
String SIMPLE_DATE_PATTERN = "^[1-9]\\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$";
/**
* 年月日日期模式无-
*/
String SIMPLE_DATE_PATTERN_SIMPLE = "^[1-9]\\d{3}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$";
/**
* 日期格式验证
*/
String DATE_FORMATE_TEMPLATE1_EREGEXP = "^[1-2][0-9][0-9][0-9]-([1][0-2]|0?[1-9])-([12][0-9]|3[01]|0?[1-9]) ([01][0-9]|[2][0-3]):[0-5][0-9]$";
/**
* 邮编格式验证
*/
String POSTCODE_EREGEXP = "^[0-9]{6}$";
}
在后续的实际运用中在具体看如何使用。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mingx</groupId>
<artifactId>mingx-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.mingx.common</groupId>
<artifactId>mingx-common</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
利用ControllerAdvice注解实现全局的异常处理,首先是针对入参校验的异常处理,会抛出MethodArgumentNotValidException的异常,首先被捕获,返回统一的参数有误响应。接下来捕获所有Exception的异常,此处异常其实可以再细化,现在为了模拟,统一捕获Exception异常,返回统一的【系统繁忙,请稍后重试】响应。
package com.mingx.common.hander;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
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.ResponseBody;
import com.mingx.common.AppResult;
import com.mingx.common.AppResultBuilder;
import com.mingx.common.ResultCode;
@ControllerAdvice
@Component
public class SysExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(SysExceptionHandler.class);
/**
* 入参校验
* @param exception
* @return
*/
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public AppResult<String> handle(MethodArgumentNotValidException exception) {
String message = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage();
return AppResultBuilder.error(ResultCode.PARAM_ERROR,message);
}
/**
* 全局异常捕捉处理
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public AppResult<String> errorHandler(Exception ex) {
logger.error(ex.getMessage(),ex);
return AppResultBuilder.error(ResultCode.SYSTEM_ERROR);
}
}
做好前面的三个基本功能的实现准备后,现在模拟实现保存用户信息的一个功能,步骤如下:
<dependency>
<groupId>com.mingx.common</groupId>
<artifactId>mingx-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
package com.mingx.user.bo;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
import com.mingx.common.RegexpContants;
import lombok.Data;
@Data
public class SaveUserBO {
@NotBlank(message = "用户名称不能为空")
@Length(max = 10,message = "长度不能超过10个字符")
private String name;
@NotBlank(message = "性别不能为空")
@Pattern(regexp = RegexpContants.ZERO_OR_ONE_EREGEXP,message = "性别格式不正确")
private String sex;
@NotBlank(message = "出生日期不能为空")
@Pattern(regexp = RegexpContants.SIMPLE_DATE_PATTERN_SIMPLE,message = "出生日期格式不正确")
private String birthday;
}
@NotBank注解即是非空注解,标识该字段不能为空
@Length注解可设置其字段长度
@Pattern注解可进行自定义正则表达式校验,可以将相关正则表达式单独抽取出来,利用之前创建的RegexpContants.java接口,其中定义常用正则表达式即可
其他更多校验方面的注解可参考hibernate Validator官方给出的参考文档,这里仅是最简单的使用。
注意:接口入参需要添加 @Validated 注解,才会对入参进行参数校验
@PostMapping("/saveUser")
public AppResult<String> saveUser(@Validated @RequestBody SaveUserBO bo){
Integer count = sysUserService.count(new QueryWrapper<SysUser>().eq("name", bo.getName()));
if(count > 0) {
return AppResultBuilder.error(ResultCode.USER_HAS_EXISTED);
}
SysUser user = new SysUser();
user.setName(bo.getName()).setSex(Integer.valueOf(bo.getSex()))
.setBirthday(LocalDate.parse(bo.getBirthday(),DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.setCreateTime(LocalDateTime.now());
sysUserService.save(user);
return AppResultBuilder.success(user.getId());
}
保存成功,则如下显示:
如果某字段为空,或者格式不正确,则会如下显示:
由于增加了重复的逻辑判断,再次保存,会返回:
本章未涉及到SpringClould相关的知识点,但是只要是一个合格的后端接口,都必须要这最基础的三点功能,简单在项目中实践,做优化提炼,有助于技术层次的提高,不能一头就扎到业务开发中,忽略了这些基础功能实现的过程。
下一章我们学习使用SpringcClould Feign,一个棒棒哒声明式伪RPC的REST客户端。
点关注,不迷路
微信搜索【寻的足迹】关注公众号,第一时间收到最新文章
SpringClould微服务架构搭建--统一响应、入参校验、异常处理(四)
原文:https://www.cnblogs.com/conswin/p/12452280.html