diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java index d8522198a..618deba5e 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/filter/ApiAccessLogFilter.java @@ -68,20 +68,20 @@ public class ApiAccessLogFilter extends OncePerRequestFilter { private void createApiAccessLog(HttpServletRequest request, Date beginTime, Map queryString, String requestBody, Exception ex) { + ApiAccessLogCreateDTO accessLog = new ApiAccessLogCreateDTO(); try { - ApiAccessLogCreateDTO accessLog = this.buildApiAccessLogDTO(request, beginTime, queryString, requestBody, ex); + this.buildApiAccessLogDTO(accessLog, request, beginTime, queryString, requestBody, ex); apiAccessLogFrameworkService.createApiAccessLogAsync(accessLog); - } catch (Exception e) { - log.error("[createApiAccessLog][url({}) 发生异常]", request.getRequestURI(), e); + } catch (Throwable th) { + log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), JsonUtils.toJsonString(accessLog), th); } } - private ApiAccessLogCreateDTO buildApiAccessLogDTO(HttpServletRequest request, Date beginTime, - Map queryString, String requestBody, Exception ex) { - ApiAccessLogCreateDTO accessLog = new ApiAccessLogCreateDTO(); + private void buildApiAccessLogDTO(ApiAccessLogCreateDTO accessLog, HttpServletRequest request, Date beginTime, + Map queryString, String requestBody, Exception ex) { // 处理用户信息 accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request)); - accessLog.setUserType(WebFrameworkUtils.getUsrType(request)); + accessLog.setUserType(WebFrameworkUtils.getUesrType(request)); // 设置访问结果 CommonResult result = WebFrameworkUtils.getCommonResult(request); if (result != null) { @@ -107,7 +107,6 @@ public class ApiAccessLogFilter extends OncePerRequestFilter { accessLog.setBeginTime(beginTime); accessLog.setEndTime(new Date()); accessLog.setDuration((int) DateUtils.diff(accessLog.getEndTime(), accessLog.getBeginTime())); - return accessLog; } } diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiErrorLogFrameworkService.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiErrorLogFrameworkService.java new file mode 100644 index 000000000..032ef40e1 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/ApiErrorLogFrameworkService.java @@ -0,0 +1,21 @@ +package cn.iocoder.dashboard.framework.logger.apilog.core.service; + +import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiErrorLogCreateDTO; + +import javax.validation.Valid; + +/** + * API 错误日志 Framework Service 接口 + * + * @author 芋道源码 + */ +public interface ApiErrorLogFrameworkService { + + /** + * 创建 API 错误日志 + * + * @param createDTO 创建信息 + */ + void createApiErrorLogAsync(@Valid ApiErrorLogCreateDTO createDTO); + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiErrorLogCreateDTO.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiErrorLogCreateDTO.java new file mode 100644 index 000000000..b351acf91 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/dto/ApiErrorLogCreateDTO.java @@ -0,0 +1,109 @@ +package cn.iocoder.dashboard.framework.logger.apilog.core.service.dto; + +import lombok.Data; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotNull; +import java.io.Serializable; +import java.util.Date; + +/** + * API 错误日志创建 DTO + * + * @author 芋道源码 + */ +@Data +@Accessors(chain = true) +public class ApiErrorLogCreateDTO implements Serializable { + + /** + * 链路编号 + */ + private String traceId; + /** + * 账号编号 + */ + private Long userId; + /** + * 用户类型 + */ + private Integer userType; + /** + * 应用名 + */ + @NotNull(message = "应用名不能为空") + private String applicationName; + + /** + * 请求方法名 + */ + @NotNull(message = "http 请求方法不能为空") + private String requestMethod; + /** + * 访问地址 + */ + @NotNull(message = "访问地址不能为空") + private String requestUrl; + /** + * 请求参数 + */ + @NotNull(message = "请求参数不能为空") + private String requestParams; + /** + * 用户 IP + */ + @NotNull(message = "ip 不能为空") + private String userIp; + /** + * 浏览器 UA + */ + @NotNull(message = "User-Agent 不能为空") + private String userAgent; + + /** + * 异常时间 + */ + @NotNull(message = "异常时间不能为空") + private Date exceptionTime; + /** + * 异常名 + */ + @NotNull(message = "异常名不能为空") + private String exceptionName; + /** + * 异常发生的类全名 + */ + @NotNull(message = "异常发生的类全名不能为空") + private String exceptionClassName; + /** + * 异常发生的类文件 + */ + @NotNull(message = "异常发生的类文件不能为空") + private String exceptionFileName; + /** + * 异常发生的方法名 + */ + @NotNull(message = "异常发生的方法名不能为空") + private String exceptionMethodName; + /** + * 异常发生的方法所在行 + */ + @NotNull(message = "异常发生的方法所在行不能为空") + private Integer exceptionLineNumber; + /** + * 异常的栈轨迹异常的栈轨迹 + */ + @NotNull(message = "异常的栈轨迹不能为空") + private String exceptionStackTrace; + /** + * 异常导致的根消息 + */ + @NotNull(message = "异常导致的根消息不能为空") + private String exceptionRootCauseMessage; + /** + * 异常导致的消息 + */ + @NotNull(message = "异常导致的消息不能为空") + private String exceptionMessage; + +} diff --git a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/package-info.java b/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/package-info.java deleted file mode 100644 index 4d76b11b2..000000000 --- a/src/main/java/cn/iocoder/dashboard/framework/logger/apilog/core/service/package-info.java +++ /dev/null @@ -1 +0,0 @@ -package cn.iocoder.dashboard.framework.logger.apilog.core.service; diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java index 95e343f51..e187b447c 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/handler/GlobalExceptionHandler.java @@ -1,12 +1,24 @@ package cn.iocoder.dashboard.framework.web.core.handler; +import cn.hutool.core.exceptions.ExceptionUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.extra.servlet.ServletUtil; import cn.iocoder.dashboard.common.exception.GlobalException; import cn.iocoder.dashboard.common.exception.ServiceException; import cn.iocoder.dashboard.common.pojo.CommonResult; +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiErrorLogFrameworkService; +import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiErrorLogCreateDTO; import cn.iocoder.dashboard.framework.security.core.util.SecurityFrameworkUtils; +import cn.iocoder.dashboard.framework.tracer.core.util.TracerUtils; +import cn.iocoder.dashboard.framework.web.core.util.WebFrameworkUtils; +import cn.iocoder.dashboard.util.json.JsonUtils; +import cn.iocoder.dashboard.util.servlet.ServletUtils; import io.github.resilience4j.ratelimiter.RequestNotPermitted; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.access.AccessDeniedException; +import org.springframework.util.Assert; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.HttpRequestMethodNotSupportedException; @@ -17,10 +29,13 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.servlet.NoHandlerFoundException; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.ValidationException; +import java.util.Date; +import java.util.Map; import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.*; @@ -33,6 +48,12 @@ import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstan @Slf4j public class GlobalExceptionHandler { + @Value("spring.application.name") + private String applicationName; + + @Resource + private ApiErrorLogFrameworkService apiErrorLogFrameworkService; + /** * 处理所有异常,主要是提供给 Filter 使用 * 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。 @@ -232,57 +253,47 @@ public class GlobalExceptionHandler { return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMessage()); } - // TODO 芋艿:增加异常日志 - public void createExceptionLog(HttpServletRequest req, Throwable e) { -// // 插入异常日志 -// SystemExceptionLogCreateDTO exceptionLog = new SystemExceptionLogCreateDTO(); -// try { -// // 增加异常计数 metrics TODO 暂时去掉 -//// EXCEPTION_COUNTER.increment(); -// // 初始化 exceptionLog -// initExceptionLog(exceptionLog, req, e); -// // 执行插入 exceptionLog -// createExceptionLog(exceptionLog); -// } catch (Throwable th) { -// log.error("[createExceptionLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th)); -// } + private void createExceptionLog(HttpServletRequest req, Throwable e) { + // 插入错误日志 + ApiErrorLogCreateDTO errorLog = new ApiErrorLogCreateDTO(); + try { + // 初始化 errorLog + initExceptionLog(errorLog, req, e); + // 执行插入 errorLog + apiErrorLogFrameworkService.createApiErrorLogAsync(errorLog); + } catch (Throwable th) { + log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th); + } } -// // TODO 优化点:后续可以增加事件 -// @Async -// public void createExceptionLog(SystemExceptionLogCreateDTO exceptionLog) { -// try { -// systemExceptionLogRpc.createSystemExceptionLog(exceptionLog); -// } catch (Throwable th) { -// log.error("[addAccessLog][插入异常日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th)); -// } -// } -// -// private void initExceptionLog(SystemExceptionLogCreateDTO exceptionLog, HttpServletRequest request, Throwable e) { -// // 设置账号编号 -// exceptionLog.setUserId(CommonWebUtil.getUserId(request)); -// exceptionLog.setUserType(CommonWebUtil.getUserType(request)); -// // 设置异常字段 -// exceptionLog.setExceptionName(e.getClass().getName()); -// exceptionLog.setExceptionMessage(ExceptionUtil.getMessage(e)); -// exceptionLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e)); -// exceptionLog.setExceptionStackTrace(ExceptionUtil.getStackTrace(e)); -// StackTraceElement[] stackTraceElements = e.getStackTrace(); -// Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空"); -// StackTraceElement stackTraceElement = stackTraceElements[0]; -// exceptionLog.setExceptionClassName(stackTraceElement.getClassName()); -// exceptionLog.setExceptionFileName(stackTraceElement.getFileName()); -// exceptionLog.setExceptionMethodName(stackTraceElement.getMethodName()); -// exceptionLog.setExceptionLineNumber(stackTraceElement.getLineNumber()); -// // 设置其它字段 -// exceptionLog.setTraceId(MallUtils.getTraceId()) -// .setApplicationName(applicationName) -// .setUri(request.getRequestURI()) -// .setQueryString(HttpUtil.buildQueryString(request)) -// .setMethod(request.getMethod()) -// .setUserAgent(HttpUtil.getUserAgent(request)) -// .setIp(HttpUtil.getIp(request)) -// .setExceptionTime(new Date()); -// } + private void initExceptionLog(ApiErrorLogCreateDTO errorLog, HttpServletRequest request, Throwable e) { + // 处理用户信息 + errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request)); + errorLog.setUserType(WebFrameworkUtils.getUesrType(request)); + // 设置异常字段 + errorLog.setExceptionName(e.getClass().getName()); + errorLog.setExceptionMessage(ExceptionUtil.getMessage(e)); + errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e)); + errorLog.setExceptionStackTrace(ExceptionUtils.getStackTrace(e)); + StackTraceElement[] stackTraceElements = e.getStackTrace(); + Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空"); + StackTraceElement stackTraceElement = stackTraceElements[0]; + errorLog.setExceptionClassName(stackTraceElement.getClassName()); + errorLog.setExceptionFileName(stackTraceElement.getFileName()); + errorLog.setExceptionMethodName(stackTraceElement.getMethodName()); + errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber()); + // 设置其它字段 + errorLog.setTraceId(TracerUtils.getTraceId()); + errorLog.setApplicationName(applicationName); + errorLog.setRequestUrl(request.getRequestURI()); + Map requestParams = MapUtil.builder() + .put("query", ServletUtil.getParamMap(request)) + .put("body", ServletUtil.getBody(request)).build(); + errorLog.setRequestParams(JsonUtils.toJsonString(requestParams)); + errorLog.setRequestMethod(request.getMethod()); + errorLog.setUserAgent(ServletUtils.getUserAgent(request)); + errorLog.setUserIp(ServletUtil.getClientIP(request)); + errorLog.setExceptionTime(new Date()); + } } diff --git a/src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java b/src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java index 1a98335e2..2cf6211ff 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java +++ b/src/main/java/cn/iocoder/dashboard/framework/web/core/util/WebFrameworkUtils.java @@ -31,7 +31,7 @@ public class WebFrameworkUtils { return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID); } - public static Integer getUsrType(HttpServletRequest request) { + public static Integer getUesrType(HttpServletRequest request) { return UserTypeEnum.ADMIN.getValue(); // TODO 芋艿:等后续优化 } diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiAccessLogDO.java b/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiAccessLogDO.java index ae8a26371..2a4d2cf19 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiAccessLogDO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiAccessLogDO.java @@ -51,6 +51,8 @@ public class InfApiAccessLogDO extends BaseDO { */ private String applicationName; + // ========== 请求相关字段 ========== + /** * 请求方法名 */ @@ -75,6 +77,8 @@ public class InfApiAccessLogDO extends BaseDO { */ private String userAgent; + // ========== 执行相关字段 ========== + /** * 开始请求时间 */ diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiErrorLogDO.java b/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiErrorLogDO.java index 1cc630bf0..1a723f9fe 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiErrorLogDO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/infra/dal/dataobject/logger/InfApiErrorLogDO.java @@ -1,4 +1,152 @@ package cn.iocoder.dashboard.modules.infra.dal.dataobject.logger; -public class InfApiErrorLogDO { +import cn.iocoder.dashboard.common.enums.UserTypeEnum; +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.dashboard.modules.infra.enums.logger.ApiErrorLogProcessStatusEnum; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.util.Date; + +/** + * API 异常数据 + * + * @author 芋道源码 + */ +@TableName("inf_api_error_log") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class InfApiErrorLogDO extends BaseDO { + + /** + * 编号 + */ + private Integer id; + /** + * 用户编号 + */ + private Integer userId; + /** + * 链路追踪编号 + * + * 一般来说,通过链路追踪编号,可以将访问日志,错误日志,链路追踪日志,logger 打印日志等,结合在一起,从而进行排错。 + */ + private String traceId; + /** + * 用户类型 + * + * 枚举 {@link UserTypeEnum} + */ + private Integer userType; + /** + * 应用名 + * + * 目前读取 spring.application.name + */ + private String applicationName; + + // ========== 请求相关字段 ========== + + /** + * 请求方法名 + */ + private String requestMethod; + /** + * 访问地址 + */ + private String requestUrl; + /** + * 请求参数 + * + * query: Query String + * body: Quest Body + */ + private String requestParams; + /** + * 用户 IP + */ + private String userIp; + /** + * 浏览器 UA + */ + private String userAgent; + + // ========== 异常相关字段 ========== + + /** + * 异常发生时间 + */ + private Date exceptionTime; + /** + * 异常名 + * + * {@link Throwable#getClass()} 的类全名 + */ + private String exceptionName; + /** + * 异常导致的消息 + * + * {@link cn.hutool.core.exceptions.ExceptionUtil#getMessage(Throwable)} + */ + private String exceptionMessage; + /** + * 异常导致的根消息 + * + * {@link cn.hutool.core.exceptions.ExceptionUtil#getRootCauseMessage(Throwable)} + */ + private String exceptionRootCauseMessage; + /** + * 异常的栈轨迹 + * + * {@link org.apache.commons.lang3.exception.ExceptionUtils#getStackTrace(Throwable)} + */ + private String exceptionStackTrace; + /** + * 异常发生的类全名 + * + * {@link StackTraceElement#getClassName()} + */ + private String exceptionClassName; + /** + * 异常发生的类文件 + * + * {@link StackTraceElement#getFileName()} + */ + private String exceptionFileName; + /** + * 异常发生的方法名 + * + * {@link StackTraceElement#getMethodName()} + */ + private String exceptionMethodName; + /** + * 异常发生的方法所在行 + * + * {@link StackTraceElement#getLineNumber()} + */ + private Integer exceptionLineNumber; + + // ========== 处理相关字段 ========== + + /** + * 处理状态 + * + * 枚举 {@link ApiErrorLogProcessStatusEnum} + */ + private Integer processStatus; + /** + * 处理时间 + */ + private Date processTime; + /** + * 处理管理员编号 + * + * 关联 {@link} + */ + private Integer processUserId; + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/enums/logger/ApiErrorLogProcessStatusEnum.java b/src/main/java/cn/iocoder/dashboard/modules/infra/enums/logger/ApiErrorLogProcessStatusEnum.java new file mode 100644 index 000000000..e48d6e6a6 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/infra/enums/logger/ApiErrorLogProcessStatusEnum.java @@ -0,0 +1,28 @@ +package cn.iocoder.dashboard.modules.infra.enums.logger; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * API 异常数据的处理状态 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum ApiErrorLogProcessStatusEnum { + + INIT(0, "未处理"), + DONE(1, "已处理"), + IGNORE(2, "已忽略"); + + /** + * 状态 + */ + private final Integer status; + /** + * 资源类型名 + */ + private final String name; + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/InfApiErrorLogService.java b/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/InfApiErrorLogService.java new file mode 100644 index 000000000..3b6b26ee2 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/InfApiErrorLogService.java @@ -0,0 +1,12 @@ +package cn.iocoder.dashboard.modules.infra.service.logger; + +import cn.iocoder.dashboard.framework.logger.apilog.core.service.ApiErrorLogFrameworkService; + +/** + * API 错误日志 Service 接口 + * + * @author 芋道源码 + */ +public interface InfApiErrorLogService extends ApiErrorLogFrameworkService { + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiAccessLogServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiAccessLogServiceImpl.java index 4200bdcfc..f0d20aec4 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiAccessLogServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiAccessLogServiceImpl.java @@ -30,7 +30,7 @@ public class InfApiAccessLogServiceImpl implements InfApiAccessLogService { @Override @Async - public void createApiAccessLogAsync(@Valid ApiAccessLogCreateDTO createDTO) { + public void createApiAccessLogAsync(ApiAccessLogCreateDTO createDTO) { // 插入 InfApiAccessLogDO apiAccessLog = InfApiAccessLogConvert.INSTANCE.convert(createDTO); apiAccessLogMapper.insert(apiAccessLog); diff --git a/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiErrorLogServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiErrorLogServiceImpl.java new file mode 100644 index 000000000..69bbeefad --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/infra/service/logger/impl/InfApiErrorLogServiceImpl.java @@ -0,0 +1,24 @@ +package cn.iocoder.dashboard.modules.infra.service.logger.impl; + +import cn.iocoder.dashboard.framework.logger.apilog.core.service.dto.ApiErrorLogCreateDTO; +import cn.iocoder.dashboard.modules.infra.service.logger.InfApiErrorLogService; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * API 错误日志 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class InfApiErrorLogServiceImpl implements InfApiErrorLogService { + + @Override + @Async + public void createApiErrorLogAsync(ApiErrorLogCreateDTO createDTO) { + + } + +}