SpringBoot 结合全局自定义异常优雅的实现记录客户操作日志

SpringBoot 结合全局自定义异常优雅的实现记录客户操作日志

场景:系统中用户所有的post请求,均要记录到操作日志表中
本案例中结合了全局自定义异常。

1 优雅的全局自定义异常

1.1 定义异常类型枚举

public enum EnumCode {
    // SUCCESS-成功,BIZERROR-业务异常,SYSERROR系统异常,PARAMSERROE传参异常
    SUCCESS,BIZERROE,SYSERROE,PARAMSERROE;

}

1.2 定义枚举缓存码映射类(参考springBoot-redis源码实现)

/**
 * 枚举缓存码映射类
 */
public final class CacheEnumConfigurations {

    private static final Map<EnumCode, EnumMapper> MAPPINGS;

    static {
        Map<EnumCode, EnumMapper> mappings = new EnumMap<>(EnumCode.class);
        /**
         * restFul 前端反馈码值枚举类型
         * SUCCESS-成功,BIZERROR-业务异常,SYSERROR系统异常
         * 200 - 成功
         * 300 - 业务异常
         * 400 - 系统异常
         * 500 - 参数异常
         */
        mappings.put(EnumCode.SUCCESS, new EnumMapper(200, "SUCCESS"));
        mappings.put(EnumCode.BIZERROE, new EnumMapper(300, "BIZERROE"));
        mappings.put(EnumCode.SYSERROE, new EnumMapper(400, "SYSERROE"));
        mappings.put(EnumCode.PARAMSERROE, new EnumMapper(600, "PARAMSERROE"));

        // 返回指定地图的不可修改视图。 这个方法允许模块为用户提供对内部的只读访问权限
        MAPPINGS = Collections.unmodifiableMap(mappings);
    }

    private CacheEnumConfigurations() {
    }

    /**
     * 根据美剧类型获取EnumMapper
     * @param enumCodeType
     * @return
     */
    public static EnumMapper getCacheEnumMapper(EnumCode enumCodeType) {
        EnumMapper enumMapper = MAPPINGS.get(enumCodeType);
        Assert.state(enumMapper != null, () -> "Unknown cache type " + enumCodeType);
        return enumMapper;
    }

    /**
     * 根据美剧类型获取对应的code
     * @param enumCodeType
     * @return
     */
    public static int getCacheEnumMapperCode(EnumCode enumCodeType) {
        EnumMapper enumMapper = getCacheEnumMapper(enumCodeType);
        return enumMapper.getCode();
    }

    /**
     * 根据美剧类型获取对应的message
     * @param enumCodeType
     * @return
     */
    public static String getCacheEnumMapperMsg(EnumCode enumCodeType) {
        EnumMapper enumMapper = getCacheEnumMapper(enumCodeType);
        return enumMapper.getMessage();
    }

}

1.3 定义restFul统一返回类

@Data
public class ResponseEntity<T> {

    @ApiModelProperty(value = "返回码", example = "200-success")
    public int code;

    @ApiModelProperty(value = "调用接口结果", example = "success, error msg")
    public String message;

    @ApiModelProperty("数据集")
    public T data;

    public ResponseEntity() {
        // 初始值
        // 200
        this.code = CacheEnumConfigurations.getCacheEnumMapperCode(EnumCode.SUCCESS);
        // SUCCESS
        this.message = CacheEnumConfigurations.getCacheEnumMapperMsg(EnumCode.SUCCESS);
    }

    public ResponseEntity failed(EnumCode enumCodeType, String message) {
        this.code = CacheEnumConfigurations.getCacheEnumMapperCode(enumCodeType);
        this.message = message;
        return this;
    }

1.4 全局自定义异常-@ControllerAdvice

/**
 * 全局异常处理
 * controller抛异常执行
 */
@ControllerAdvice // 标志位全局异常处理类
@ResponseBody // 所有返回给前端的view指定为json
public class GlobalExceptionHandler {

    public static final Logger logger = LoggerFactory.getLogger(TestController.class);


    @Autowired
    private LanguageService languageService;

    @Autowired
    private ContextService contextService;

    /**
     * 处理请求方式(get,post,delete,put等)错误的异常
     */
    @ExceptionHandler(value = {HttpRequestMethodNotSupportedException.class})
    public ResponseEntity handlerExp(HttpRequestMethodNotSupportedException e) {
        String exMsg = ExceptionUtil.getStackTrace(e);
        logger.error("### 系统异常: {}", exMsg);
        return failedResponse(EnumCode.SYSERROE, e.getMessage());
    }

    /**
     * 处理请求URL缺少必要参数的异常
     */
    @ExceptionHandler(value = {MissingServletRequestParameterException.class})
    public ResponseEntity handlerExp(MissingServletRequestParameterException e) {
        String exMsg = ExceptionUtil.getStackTrace(e);
        logger.error("### 系统异常: {}", exMsg);
        return failedResponse(EnumCode.SYSERROE, e.getMessage());
    }

    /**
     * 处理自定义异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(GlobalException.class)
    public ResponseEntity bizException(GlobalException ex) {
        logger.error("### 业务异常: {}", ex.getErrorMsg());
        // 需要做中英日文翻译
        String languageType = contextService.getRequestHeadersMap().get(HeaderReqConstant.LANGUAGE_TYPE);
        String businessMsg = languageService.getBusinessMsg(ex.getErrorMsg(), languageType);
        if (StringUtils.isNotBlank(businessMsg)) {
            ex.setErrorMsg(businessMsg);
        }
        return failedResponse(ex.getEnumCodeType(), ex.getErrorMsg());
    }


    /**
     * 其他异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity unknownException(Exception ex) {
        String exMsg = ExceptionUtil.getStackTrace(ex);
        logger.error("### 系统异常: {}", exMsg);
        return failedResponse(EnumCode.SYSERROE, ex.getMessage());
    }

    /**
     * 参数校验异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = {MethodArgumentNotValidException.class, ConstraintViolationException.class})
    public ResponseEntity ValidationException(Exception ex) {
        String msg = "";
        if (ex instanceof MethodArgumentNotValidException) {
            msg = handlerMethodArgumentNotValidException((MethodArgumentNotValidException) ex);
        } else if (ex instanceof ConstraintViolationException) {
            msg = handlerConstraintViolationException((ConstraintViolationException) ex);
        } else {
            msg = ex.getMessage();
        }
        logger.error("### 参数校验异常: {}", msg);
        return failedResponse(EnumCode.PARAMSERROE, msg);
    }


    /**
     * MethodArgumentNotValidExceptiony 参数异常解析
     * 当对用{@code @Valid}注释的参数进行验证失败时,将引发异常。
     * @param ex
     * @return
     */
    public String handlerMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        // 需要中英日文翻译
        String languageType = contextService.getRequestHeadersMap().get(HeaderReqConstant.LANGUAGE_TYPE);
        // 参数解析
        StringBuffer msgBuilder = new StringBuffer();
        List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
        if (!CollectionUtils.isEmpty(allErrors)) {
            // 遍历
            for (ObjectError objectError : allErrors) {
                String paramsMsg = languageService.getParamsMsg(objectError.getDefaultMessage(), languageType);
                if (StringUtils.isNotBlank(paramsMsg)) {
                    msgBuilder.append(paramsMsg).append(",");
                } else {
                    msgBuilder.append(objectError.getDefaultMessage()).append(",");
                }
            }
        }
        // 切割最末的逗号
        return msgBuilder.toString().substring(0, msgBuilder.length() - 1);
    }

    /**
     * ConstraintViolationException 参数异常解析
     * 违反约束
     * @param ex
     * @return
     */
    private String handlerConstraintViolationException(ConstraintViolationException ex) {
        // 需要中英日文翻译
        String languageType = contextService.getRequestHeadersMap().get(HeaderReqConstant.LANGUAGE_TYPE);
        // 参数解析
        StringBuffer msgBuilder = new StringBuffer();
        return msgBuilder.toString();
    }


    public ResponseEntity failedResponse(EnumCode enumCodeType, String msg) {
        return new ResponseEntity().failed(enumCodeType, msg);
    }

}

2 优雅的实现记录客户操作日志

2.1 swagger2依赖包

swagger2 API依赖

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>

2.2 附上operation_log表结构

CREATE TABLE `operation_log` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `user_id` bigint(11) DEFAULT NULL COMMENT '用户id',
  `user_name` varchar(128) DEFAULT NULL COMMENT '用户名称',
  `login_name` varchar(128) DEFAULT NULL COMMENT '用户登陆名称',
  `code` varchar(128) DEFAULT NULL COMMENT '操作结果码 success-200',
  `msg` text COMMENT '操作结果码 success-200',
  `uri` varchar(128) DEFAULT NULL COMMENT '操作请求路径',
  `operation_value` text COMMENT '操作名称',
  `parames` text COMMENT '上报请求参数',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='用户操作日志记录表';

2.3 配置记录客户操作日志执行点-@ControllerAdvice

实现ResponseBodyAdvice
T伟controller返回的统一restFul对象

/**
 * 执行点:所有的controller方法return之后执行
 */
@ControllerAdvice
public class OperationLogAdvice implements ResponseBodyAdvice<ResponseEntity> {

    @Autowired
    private ContextService contextService;

    @Autowired
    private OperationLogService operationLogService;

    /**
     * 判断是否需要执行beforeBodyWrite
     * @param returnType
     * @param converterType
     * @return true-执行 false-不执行
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 所有的post操作都执行beforeBodyWrite记录操作日志
        if(contextService.getHttpServletRequest().getMethod().equals("POST")) {
            return true;
        }
        return false;
    }

    @Override
    public ResponseEntity beforeBodyWrite(ResponseEntity responseEntity, MethodParameter methodParameter, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        operationLogService.recordLog(responseEntity, methodParameter);

        return responseEntity;
    }
}

2.4 记录日志到数据库

@Service
public class OperationLogImpl implements OperationLogService {

    public static final Logger logger = LoggerFactory.getLogger(OperationLogService.class);

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Autowired
    private ContextService contextService;

    @Autowired
    private OperationLogMapperExt operationLogMapperExt;

    /**
     * 记录用户操作日志
     */
    @Override
    public void recordLog(ResponseEntity responseEntity, MethodParameter methodParameter) {
        try {
            // 静态获取request
            HttpServletRequest httpServletRequest = contextService.getHttpServletRequest();
            // 获取所有报文头
            Map<String, String> requestHeadersMap = contextService.getRequestHeadersMap();
            String userId = requestHeadersMap.get(HeaderReqConstant.USER_ID);
            String userName = requestHeadersMap.get(HeaderReqConstant.USER_NAME);
            String loginName = requestHeadersMap.get(HeaderReqConstant.LOGIN_NAME);
            // 用户userId为空时不记录
            if (StringUtils.isBlank(userId)) {
                return;
            }
            OperationLogWithBLOBs operationLog = new OperationLogWithBLOBs();
            operationLog.setUserId(Long.valueOf(userId));
            operationLog.setUserName(userName);
            operationLog.setLoginName(loginName);
            operationLog.setCode(String.valueOf(responseEntity.getCode()));
            operationLog.setMsg(responseEntity.getMessage());
            operationLog.setUri(httpServletRequest.getRequestURI());
            operationLog.setCreateTime(new Date());
            operationLog.setParames(contextService.getRequestParamsMap().toString());
            // 获取@ApiOperation上的value
            Map<RequestMappingInfo, HandlerMethod> handlerMethodsMap = requestMappingHandlerMapping.getHandlerMethods();
            for (Map.Entry<RequestMappingInfo, HandlerMethod> item : handlerMethodsMap.entrySet()) {
                RequestMappingInfo uriMappingInfo = item.getKey();
                String uriKey = uriMappingInfo.getPatternsCondition().toString();
                if (operationLog.getUri().equals(uriKey.substring(1, uriKey.length() - 1))) {
                    HandlerMethod method = item.getValue();
                    ApiOperation annotation = method.getMethod().getDeclaredAnnotation(ApiOperation.class);
                    if (annotation != null) {
                        operationLog.setOperationValue(annotation.value());
                    }
                    break;
                }
            }
            operationLogMapperExt.insert(operationLog);

        } catch (Exception e) {
            logger.error(e.getMessage());
        }
    }
}

2.5 数据库记录结果

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值