学而时习之,不亦说乎
前言
对于一个由后端提供的接口来说,有一个统一的响应格式,方便入参校验,统一的异常处理,是必不可少的,今天我们将这三个基础功能集成到项目中,使项目更贴近实际的开发场景。
统一响应
在项目开发中,一般返回给前端的都会是一个统一的返回响应对象,因此后端需要封装一个泛型类来作为响应对象,这样做的好处是前后端能统一接口返回,可以做规范的响应处理。
实现步骤:
- 创建mingx-common微服务,用于处理公共业务,包括统一响应,统一异常拦截,工具类等。
- 在src/mian/java下新建com.mingx.common包,在此包下面创建
AppResult.java,AppResultBuilder.java,ResultCode.java文件,作用分别如下:
- AppResult.java:规范返回结果的格式的类
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;
}
}
- AppResultBuilder.java:方便返回格式组装的类
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;
}
}
- ResultCode.java:枚举类,定义返回信息
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 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。
准备步骤:
- 在父pom中增加hibernate validator主版本管理
<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>
- 在src/mian/java下新建com.mingx.common包,在此包下面创建RegexpContants接口,用于常用正则表达式管理,根据项目中的实际情况添加。
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}$";
}
在后续的实际运用中在具体看如何使用。
统一异常响应
- 在mingx-common的pom中引入spring-boot-starter-web依赖
<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>
- 在src/mian/java下新建com.mingx.common,hander包,在此包下面创建SysExceptionHandler.java
利用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);
}
}
综合实践
做好前面的三个基本功能的实现准备后,现在模拟实现保存用户信息的一个功能,步骤如下:
- 在项目pom中引入mingx-common、hibernate-validator依赖
<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>
- 在mingx-user-pojo项目中创建com.mingx.user.bo包,该包专门放校验入参的pojo类,创建SaveUserBO.java类,建议BO类的取名方式为:对应方法名 + BO,具体代码如下:
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官方给出的参考文档,这里仅是最简单的使用。
- 在mingx-user项目的SysController.java中,增加保存用户信息接口:
注意:接口入参需要添加 @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());
}
- 启动nacos,启动mingx-user,打开postman,发送post请求,访问
http://localhost:7001/user/saveUser,
在body中加入json格式的参数,可如下:
保存成功,则如下显示:
如果某字段为空,或者格式不正确,则会如下显示:
由于增加了重复的逻辑判断,再次保存,会返回:
结束语
本章未涉及到SpringClould相关的知识点,但是只要是一个合格的后端接口,都必须要这最基础的三点功能,简单在项目中实践,做优化提炼,有助于技术层次的提高,不能一头就扎到业务开发中,忽略了这些基础功能实现的过程。
下一章我们学习使用SpringcClould Feign,一个棒棒哒声明式伪RPC的REST客户端。
点关注,不迷路
微信搜索【寻的足迹】关注公众号,第一时间收到最新文章