• 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客户端。

    点关注,不迷路

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

  • 相关阅读:
    微信公众号开发问题总结(坑)
    map 取值报空指针,明明值存在
    关于客户端Socket连接服务端,客户端只有在服务端关闭后才收到数据的问题
    关于Spine动画,关于cocosCreator
    cocsoCreator实现镜头跟随节点移动
    cocos节点添加Touch监听代码
    使用git管理你的代码,使用git托管代码至gitee
    关于ccoosCreator里的物理系统
    Python Scrapy 爬虫简单教程
    quasar 参考文档
  • 原文地址:https://www.cnblogs.com/conswin/p/12452280.html
Copyright © 2020-2023  润新知