实战分布式医疗挂号通用模块统一返回结果异常日志处理

作者:Hudie. 时间:2022-01-28 16:31:32 

文章导读

本系列文章介绍从0开始搭建一个基于分布式的医疗挂号系统。本次四篇文章完成了医院设置微服务模块的后端接口,为了方便开发,对接口的返回结果、全局异常、全局日志进行了统一处理。 同时,为了方便进行访问测试,还整合了Swagger2工具,这些通用的模块中,除了全局日志被放在医院设置微服务模块的配置资源中,其余都统一被抽取在common模块中。具体实现可参考下面文章:

医院设置微服务 | 模块搭建

医院设置微服务 | 接口开发

通用模块 | 整合Swagger2

项目已开源至  https://github.com/Guoqianliang/yygh_parent

一、统一返回结果

在实际开发中,是一个后端团队一起开发,每个人做不同的模块,开发不同的接口,最终进行调用进而显示。因此可以把所有返回结果做一个统一的约定。让所有的接口都返回相同的数据格式,这样利于前端的显示与解析。

实战分布式医疗挂号通用模块统一返回结果异常日志处理

上图这一操作需要通过统一返回结果类 和统一返回结果状态信息类 来实现。

1.统一返回结果类

/**
* 全局统一返回结果类
*/
@Data
@ApiModel(value = "全局统一返回结果")
public class Result<T> {
   @ApiModelProperty(value = "返回码")
   private Integer code;
   @ApiModelProperty(value = "返回消息")
   private String message;
   @ApiModelProperty(value = "返回数据")
   private T data;
   public Result() {
   }
   protected static <T> Result<T> build(T data) {
       Result<T> result = new Result<T>();
       if (data != null) {
           result.setData(data);
       }
       return result;
   }
   public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
       Result<T> result = build(body);
       result.setCode(resultCodeEnum.getCode());
       result.setMessage(resultCodeEnum.getMessage());
       return result;
   }
   public static <T> Result<T> build(Integer code, String message) {
       Result<T> result = build(null);
       result.setCode(code);
       result.setMessage(message);
       return result;
   }
   public static <T> Result<T> ok() {
       return Result.ok(null);
   }
   /**
    * 操作成功
    * @param data
    * @param <T>
    * @return
    */
   public static <T> Result<T> ok(T data) {
       Result<T> result = build(data);
       return build(data, ResultCodeEnum.SUCCESS);
   }
   public static <T> Result<T> fail() {
       return Result.fail(null);
   }
   /**
    * 操作失败
    * @param data
    * @param <T>
    * @return
    */
   public static <T> Result<T> fail(T data) {
       Result<T> result = build(data);
       return build(data, ResultCodeEnum.FAIL);
   }
   public Result<T> message(String msg) {
       this.setMessage(msg);
       return this;
   }
   public Result<T> code(Integer code) {
       this.setCode(code);
       return this;
   }
   public boolean isOk() {
       if (this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
           return true;
       }
       return false;
   }
}

2.统一返回状态信息类

/**
* 统一返回结果状态信息类
*/
@Getter
public enum ResultCodeEnum {
   SUCCESS(200, "成功"),
   FAIL(201, "失败"),
   PARAM_ERROR(202, "参数不正确"),
   SERVICE_ERROR(203, "服务异常"),
   DATA_ERROR(204, "数据异常"),
   DATA_UPDATE_ERROR(205, "数据版本异常"),
   LOGIN_AUTH(208, "未登陆"),
   PERMISSION(209, "没有权限"),
   CODE_ERROR(210, "验证码错误"),
   //    LOGIN_MOBLE_ERROR(211, "账号不正确"),
   LOGIN_DISABLED_ERROR(212, "改用户已被禁用"),
   REGISTER_MOBLE_ERROR(213, "手机号已被使用"),
   LOGIN_AURH(214, "需要登录"),
   LOGIN_ACL(215, "没有权限"),
   URL_ENCODE_ERROR(216, "URL编码失败"),
   ILLEGAL_CALLBACK_REQUEST_ERROR(217, "非法回调请求"),
   FETCH_ACCESSTOKEN_FAILD(218, "获取accessToken失败"),
   FETCH_USERINFO_ERROR(219, "获取用户信息失败"),
   //LOGIN_ERROR( 23005, "登录失败"),
   PAY_RUN(220, "支付中"),
   CANCEL_ORDER_FAIL(225, "取消订单失败"),
   CANCEL_ORDER_NO(225, "不能取消预约"),
   HOSCODE_EXIST(230, "医院编号已经存在"),
   NUMBER_NO(240, "可预约号不足"),
   TIME_NO(250, "当前时间不可以预约"),
   SIGN_ERROR(300, "签名错误"),
   HOSPITAL_OPEN(310, "医院未开通,暂时不能访问"),
   HOSPITAL_LOCK(320, "医院被锁定,暂时不能访问"),
   ;
   private Integer code;
   private String message;
   private ResultCodeEnum(Integer code, String message) {
       this.code = code;
       this.message = message;
   }
}

二、统一异常处理

spring boot 默认情况下会将异常映射到 /error 进行异常处理,这样的提示十分不友好,下面使用自定义异常处理,可以提供更友好的展示。

1.自定义异常类

/**
* 自定义全局异常类
*
*/
@Data
@ApiModel(value = "自定义全局异常类")
public class YyghException extends RuntimeException {
   @ApiModelProperty(value = "异常状态码")
   private Integer code;
   /**
    * 通过状态码和错误消息创建异常对象
    * @param message
    * @param code
    */
   public YyghException(String message, Integer code) {
       super(message);
       this.code = code;
   }
   /**
    * 接收枚举类型对象
    * @param resultCodeEnum
    */
   public YyghException(ResultCodeEnum resultCodeEnum) {
       super(resultCodeEnum.getMessage());
       this.code = resultCodeEnum.getCode();
   }
   @Override
   public String toString() {
       return "YyghException{" +
               "code=" + code +
               ", message=" + this.getMessage() +
               '}';
   }
}

2.全局异常处理

下面代码中两个方法的@ExceptionHandler注解,分别传入系统异常Exception类和自定义异常YyghException类,当出现系统异常时会运行系统的Exception方法,当出现自定义异常时会运行YyghException方法。

/**
* @Description: 统一异常处理类
* @author Guoqianliang
* @date 20:56 - 2021/4/7
*/
@ControllerAdvice
public class GlobalExceptionHandler {
   /**
    * 全局异常处理
    * @param e
    * @return
    */
   @ExceptionHandler(Exception.class)
   @ResponseBody
   public Result error(Exception e) {
       e.printStackTrace();
       return Result.fail();
   }
   /**
    * 自定义异常处理
    * @param e
    * @return
    */
   @ExceptionHandler(YyghException.class)
   @ResponseBody
   public Result error(YyghException e) {
       e.printStackTrace();
       return Result.fail();
   }
}

使用自定义异常时,不会自动调用,需要手动抛出异常,举例如下:

实战分布式医疗挂号通用模块统一返回结果异常日志处理

三、统一日志处理

日志记录器(Logger)的行为是分等级的,常用的4个级别如下:

DEBUG < INFO < WARN < ERROR

级别越高,打印的信息越多。默认情况下,springboot从控制台打印出来的日志级别只有INFO及以上级别,通过:logging.level.root=debug可以修改日志级别。

下面给出一个日志模块,通过此模板可以将日志持久化到本地文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
   <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
   <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
   <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
   <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
   <contextName>logback</contextName>
   <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会 * 入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
   <property name="log.path" value="E:/IntelliJ_IDEA/workspace/yygh_parent/yygh_log"/>
   <!-- 彩色日志 -->
   <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
   <!-- magenta:洋红 -->
   <!-- boldMagenta:粗红-->
   <!-- cyan:青色 -->
   <!-- white:白色 -->
   <!-- magenta:洋红 -->
   <property name="CONSOLE_LOG_PATTERN"
             value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
   <!--输出到控制台-->
   <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
       <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
       <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
       <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
           <level>INFO</level>
       </filter>
       <encoder>
           <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
           <!-- 设置字符集 -->
           <charset>UTF-8</charset>
       </encoder>
   </appender>
   <!--输出到文件-->
   <!-- 时间滚动输出 level为 INFO 日志 -->
   <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <!-- 正在记录的日志文件的路径及文件名 -->
       <file>${log.path}/log_info.log</file>
       <!--日志文件输出格式-->
       <encoder>
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
           <charset>UTF-8</charset>
       </encoder>
       <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
           <!-- 每天日志归档路径以及格式 -->
           <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
           <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
               <maxFileSize>100MB</maxFileSize>
           </timeBasedFileNamingAndTriggeringPolicy>
           <!--日志文件保留天数-->
           <maxHistory>15</maxHistory>
       </rollingPolicy>
       <!-- 此日志文件只记录info级别的 -->
       <filter class="ch.qos.logback.classic.filter.LevelFilter">
           <level>INFO</level>
           <onMatch>ACCEPT</onMatch>
           <onMismatch>DENY</onMismatch>
       </filter>
   </appender>
   <!-- 时间滚动输出 level为 WARN 日志 -->
   <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <!-- 正在记录的日志文件的路径及文件名 -->
       <file>${log.path}/log_warn.log</file>
       <!--日志文件输出格式-->
       <encoder>
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
           <charset>UTF-8</charset> <!-- 此处设置字符集 -->
       </encoder>
       <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
           <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
           <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
               <maxFileSize>100MB</maxFileSize>
           </timeBasedFileNamingAndTriggeringPolicy>
           <!--日志文件保留天数-->
           <maxHistory>15</maxHistory>
       </rollingPolicy>
       <!-- 此日志文件只记录warn级别的 -->
       <filter class="ch.qos.logback.classic.filter.LevelFilter">
           <level>warn</level>
           <onMatch>ACCEPT</onMatch>
           <onMismatch>DENY</onMismatch>
       </filter>
   </appender>
   <!-- 时间滚动输出 level为 ERROR 日志 -->
   <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <!-- 正在记录的日志文件的路径及文件名 -->
       <file>${log.path}/log_error.log</file>
       <!--日志文件输出格式-->
       <encoder>
           <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
           <charset>UTF-8</charset> <!-- 此处设置字符集 -->
       </encoder>
       <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
       <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
           <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
           <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
               <maxFileSize>100MB</maxFileSize>
           </timeBasedFileNamingAndTriggeringPolicy>
           <!--日志文件保留天数-->
           <maxHistory>15</maxHistory>
       </rollingPolicy>
       <!-- 此日志文件只记录ERROR级别的 -->
       <filter class="ch.qos.logback.classic.filter.LevelFilter">
           <level>ERROR</level>
           <onMatch>ACCEPT</onMatch>
           <onMismatch>DENY</onMismatch>
       </filter>
   </appender>
   <!--
       <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
       <logger>仅有一个name属性,
       一个可选的level和一个可选的addtivity属性。
       name:用来指定受此logger约束的某一个包或者具体的某一个类。
       level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
             如果未设置此属性,那么当前logger将会继承上级的级别。
   -->
   <!--
       使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
       第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
       第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
    -->
   <!--开发环境:打印控制台-->
   <springProfile name="dev">
       <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
       <logger name="com.guli" level="INFO"/>
       <!--
           root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
           level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
           可以包含零个或多个appender元素。
       -->
       <root level="INFO">
           <appender-ref ref="CONSOLE"/>
           <appender-ref ref="INFO_FILE"/>
           <appender-ref ref="WARN_FILE"/>
           <appender-ref ref="ERROR_FILE"/>
       </root>
   </springProfile>
   <!--生产环境:输出到文件-->
   <springProfile name="pro">
       <root level="INFO">
           <appender-ref ref="CONSOLE"/>
           <appender-ref ref="DEBUG_FILE"/>
           <appender-ref ref="INFO_FILE"/>
           <appender-ref ref="ERROR_FILE"/>
           <appender-ref ref="WARN_FILE"/>
       </root>
   </springProfile>
</configuration>

来源:https://blog.csdn.net/weixin_43691058/article/details/115447130

标签:分布式,医疗挂号,统一返回,异常处理,日志处理
0
投稿

猜你喜欢

  • Android中Service与Activity之间通信的几种方式

    2023-10-11 15:40:16
  • Mybatis-Plus 全局配置无效的解决方案

    2022-06-29 12:46:02
  • Java如何跳过https的ssl证书验证详解

    2023-08-24 11:34:56
  • JAVA基本概念详解

    2022-06-09 11:54:53
  • java文件处理工具类详解

    2022-12-19 22:49:59
  • springcloud项目占用内存好几个G导致服务器崩溃的问题

    2023-03-30 09:54:25
  • Android仿美团外卖菜单界面

    2022-03-28 02:28:35
  • Android 资源 id详解及的动态获取

    2023-06-25 00:32:21
  • java多线程CountDownLatch与线程池ThreadPoolExecutor/ExecutorService案例

    2021-06-21 12:29:50
  • Java 回调函数深入理解

    2023-11-01 17:32:04
  • C语言实现扫雷小游戏完整算法详解(附完整代码)

    2021-10-30 05:06:58
  • jpa实现多对多的属性时查询的两种方法

    2022-01-14 00:47:45
  • Springboot一个注解搞定返回参数key转换功能

    2022-06-21 00:11:24
  • 如何利用Jackson序列化忽略指定类型的属性详解

    2023-11-16 09:01:27
  • springboot @ConfigurationProperties和@PropertySource的区别

    2023-06-12 07:18:14
  • java安全编码指南之:Number操作详解

    2021-09-27 07:14:50
  • Android开发Popwindow仿微信右上角下拉菜单实例代码

    2023-07-17 19:54:09
  • C#使用Gembox.SpreadSheet向Excel写入数据及图表的实例

    2021-09-30 01:58:54
  • java爬虫Jsoup主要类及功能使用详解

    2023-09-26 18:02:25
  • 4种Android屏幕自适应解决方案

    2022-09-09 07:28:42
  • asp之家 软件编程 m.aspxhome.com