SpringClould微服务架构搭建--统一响应、入参校验、异常处理(四)

学而时习之,不亦说乎

前言

对于一个由后端提供的接口来说,有一个统一的响应格式,方便入参校验,统一的异常处理,是必不可少的,今天我们将这三个基础功能集成到项目中,使项目更贴近实际的开发场景。

统一响应

在项目开发中,一般返回给前端的都会是一个统一的返回响应对象,因此后端需要封装一个泛型类来作为响应对象,这样做的好处是前后端能统一接口返回,可以做规范的响应处理。
实现步骤:

  1. 创建mingx-common微服务,用于处理公共业务,包括统一响应,统一异常拦截,工具类等。
  2. 在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 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

准备步骤:

  1. 在父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>
  1. 在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}$";
}

在后续的实际运用中在具体看如何使用。

统一异常响应

  1. 在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>
  1. 在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);
    }

}

综合实践

做好前面的三个基本功能的实现准备后,现在模拟实现保存用户信息的一个功能,步骤如下:

  1. 在项目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>
  1. 在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官方给出的参考文档,这里仅是最简单的使用。

  1. 在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());

}
  1. 启动nacos,启动mingx-user,打开postman,发送post请求,访问
    http://localhost:7001/user/saveUser
    在body中加入json格式的参数,可如下:

保存成功,则如下显示:

如果某字段为空,或者格式不正确,则会如下显示:

由于增加了重复的逻辑判断,再次保存,会返回:

结束语

本章未涉及到SpringClould相关的知识点,但是只要是一个合格的后端接口,都必须要这最基础的三点功能,简单在项目中实践,做优化提炼,有助于技术层次的提高,不能一头就扎到业务开发中,忽略了这些基础功能实现的过程。

下一章我们学习使用SpringcClould Feign,一个棒棒哒声明式伪RPC的REST客户端。

点关注,不迷路

微信搜索【寻的足迹】关注公众号,第一时间收到最新文章

原文地址:https://www.cnblogs.com/conswin/p/12452280.html