文章目录
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());
}
}
}