微服务全局日志处理

文章目录


描述

由于加全局日志,WebLog注解没有起到作用

一、持久化部分

表:

sql 复制代码
-- 1.提货点
drop table if exists `base_operation_log`;
CREATE TABLE `base_operation_log`
(
    `id`             bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
    `request_ip`     varchar(50)  DEFAULT '' COMMENT '操作ID',
    `type`           int          DEFAULT NULL COMMENT '日志类型 1.操作类型 2.异常异常',
    `staff_name`     varchar(50)  DEFAULT '' COMMENT '操作人',
    `description`    varchar(512) DEFAULT '' COMMENT '描述',
    `biz_id`         bigint       DEFAULT NULL COMMENT '业务ID',
    `class_path`     varchar(255) DEFAULT '' COMMENT '类路径',
    `action_method`  varchar(50)  DEFAULT NULL COMMENT '请求方法',
    `request_uri`    varchar(255) DEFAULT NULL COMMENT '请求地址',
    `http_method`    varchar(10)  DEFAULT NULL COMMENT '请求类型:GET:GET请求;POST:POST请求;PUT:PUT请求;DELETE:DELETE请求;PATCH:PATCH请求;TRACE:TRACE请求;HEAD:HEAD请求;OPTIONS:OPTIONS请求',
    `params`         text COMMENT '参数',
    `result`         text COMMENT '返回值',
    `ex_detail`      text COMMENT '异常描述',
    `start_time`     datetime     DEFAULT NULL COMMENT '开始时间',
    `finish_time`    datetime     DEFAULT NULL COMMENT '结束时间',
    `consuming_time` bigint       DEFAULT NULL COMMENT '消耗时间',
    `ua`             varchar(500) DEFAULT NULL COMMENT '浏览器',
    `create_time`    datetime     DEFAULT NULL COMMENT '创建时间',
    `create_user_id` bigint       DEFAULT '0' COMMENT '创建人',
    `update_time`    datetime     DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
    `update_user_id` bigint       DEFAULT '0' COMMENT '更新人',
    `create_org_id`  bigint       DEFAULT '0' COMMENT '创建组织ID',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='操作日志';

1.1 controller

java 复制代码
package com.ulinkle.base.controller;

import com.ulinkle.base.api.BaseOperationLogService;
import com.ulinkle.common.core.entity.BaseOperationLogSaveAo;
import com.ulinkle.common.core.utils.BeanPlusUtil;
import com.ulinkle.domain.base.entity.BaseOperationLog;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 王
 * @description
 * @createDate
 */
@RestController
@RequestMapping("/operateLog")
@Api(tags = "操作日志")
@AllArgsConstructor
public class BaseOperateLogController {
    private final BaseOperationLogService baseOperationLogService;

    @PostMapping("add")
    public void add(@RequestBody BaseOperationLogSaveAo saveAo) {
        baseOperationLogService.save(BeanPlusUtil.toBean(saveAo, BaseOperationLog.class));
    }
}

1.2 service

java 复制代码
import com.baomidou.mybatisplus.extension.service.IService;
import com.ulinkle.domain.base.entity.BaseOperationLog;

/**
* @author DELL
* @description 针对表【base_operation_log(操作日志)】的数据库操作Service
* @createDate 2024-10-17 14:53:59
*/
public interface BaseOperationLogService extends IService<BaseOperationLog> {

}

1.3 service impl

java 复制代码
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ulinkle.base.api.BaseOperationLogService;
import com.ulinkle.base.mapper.BaseOperationLogMapper;
import com.ulinkle.domain.base.entity.BaseOperationLog;
import org.springframework.stereotype.Service;

/**
* @author DELL
* @description 针对表【base_operation_log(操作日志)】的数据库操作Service实现
* @createDate 2024-10-17 14:53:59
*/
@Service
public class BaseOperationLogServiceImpl extends ServiceImpl<BaseOperationLogMapper, BaseOperationLog>
    implements BaseOperationLogService{

}

1.4 mapper

java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ulinkle.domain.base.entity.BaseOperationLog;

/**
* @author DELL
* @description 针对表【base_operation_log(操作日志)】的数据库操作Mapper
* @createDate 2024-10-17 14:53:59
* @Entity com.ulinkle.domain.base.entity.BaseOperationLog
*/
public interface BaseOperationLogMapper extends BaseMapper<BaseOperationLog> {

}

BaseOperationLogMapper.xml

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ulinkle.base.mapper.BaseOperationLogMapper">

    <resultMap id="BaseResultMap" type="com.ulinkle.domain.base.entity.BaseOperationLog">
            <id property="id" column="id" jdbcType="BIGINT"/>
            <result property="requestIp" column="request_ip" jdbcType="VARCHAR"/>
            <result property="type" column="type" jdbcType="INTEGER"/>
            <result property="staffName" column="staff_name" jdbcType="VARCHAR"/>
            <result property="description" column="description" jdbcType="VARCHAR"/>
            <result property="bizId" column="biz_id" jdbcType="BIGINT"/>
            <result property="classPath" column="class_path" jdbcType="VARCHAR"/>
            <result property="actionMethod" column="action_method" jdbcType="VARCHAR"/>
            <result property="requestUri" column="request_uri" jdbcType="VARCHAR"/>
            <result property="httpMethod" column="http_method" jdbcType="VARCHAR"/>
            <result property="params" column="params" jdbcType="VARCHAR"/>
            <result property="result" column="result" jdbcType="VARCHAR"/>
            <result property="exDetail" column="ex_detail" jdbcType="VARCHAR"/>
            <result property="startTime" column="start_time" jdbcType="TIMESTAMP"/>
            <result property="finishTime" column="finish_time" jdbcType="TIMESTAMP"/>
            <result property="consumingTime" column="consuming_time" jdbcType="BIGINT"/>
            <result property="ua" column="ua" jdbcType="VARCHAR"/>
            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
            <result property="createUserId" column="create_user_id" jdbcType="BIGINT"/>
            <result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
            <result property="updateUserId" column="update_user_id" jdbcType="BIGINT"/>
            <result property="createOrgId" column="create_org_id" jdbcType="BIGINT"/>
    </resultMap>

    <sql id="Base_Column_List">
        id,request_ip,type,
        staff_name,description,biz_id,
        class_path,action_method,request_uri,
        http_method,params,result,
        ex_detail,start_time,finish_time,
        consuming_time,ua,create_time,
        create_user_id,update_time,update_user_id,
        create_org_id
    </sql>
</mapper>

1.5实体entity

java 复制代码
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ulinkle.common.core.entity.LogTypeEnum;
import com.ulinkle.common.core.entity.SuperEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
 * 操作日志
 * @TableName base_operation_log
 */
@EqualsAndHashCode(callSuper = true)
@TableName(value ="base_operation_log")
@Data
public class BaseOperationLog extends SuperEntity<Long> implements Serializable {
    /**
     * 操作ID
     */
    @TableField(value = "request_ip")
    private String requestIp;

    /**
     * 日志类型 1.操作类型 2.异常异常
     */
    @TableField(value = "type")
    private LogTypeEnum type;

    /**
     * 操作人
     */
    @TableField(value = "staff_name")
    private String staffName;

    /**
     * 描述
     */
    @TableField(value = "description")
    private String description;

    /**
     * 业务ID
     */
    @TableField(value = "biz_id")
    private Long bizId;

    /**
     * 类路径
     */
    @TableField(value = "class_path")
    private String classPath;

    /**
     * 请求方法
     */
    @TableField(value = "action_method")
    private String actionMethod;

    /**
     * 请求地址
     */
    @TableField(value = "request_uri")
    private String requestUri;

    /**
     * 请求类型:GET:GET请求;POST:POST请求;PUT:PUT请求;DELETE:DELETE请求;PATCH:PATCH请求;TRACE:TRACE请求;HEAD:HEAD请求;OPTIONS:OPTIONS请求
     */
    @TableField(value = "http_method")
    private String httpMethod;

    /**
     * 参数
     */
    @TableField(value = "params")
    private String params;

    /**
     * 返回值
     */
    @TableField(value = "result")
    private String result;

    /**
     * 异常描述
     */
    @TableField(value = "ex_detail")
    private String exDetail;

    /**
     * 开始时间
     */
    @TableField(value = "start_time")
    private LocalDateTime startTime;

    /**
     * 结束时间
     */
    @TableField(value = "finish_time")
    private LocalDateTime finishTime;

    /**
     * 消耗时间
     */
    @TableField(value = "consuming_time")
    private Long consumingTime;

    /**
     * 浏览器
     */
    @TableField(value = "ua")
    private String ua;

    @TableField(value = "create_org_id",fill = FieldFill.INSERT)
    private Integer createOrgId;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

二.核心部分

1.1注解

java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 操作日志注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
public @interface WebLog {
    /**
     * 是否启用 操作日志
     *
     * @return 是否启用
     */
    boolean enabled() default true;

    /**
     * @return {String}
     */
    String value() default "";

    /** 模块 */
    String modular() default "";

    /**
     * 是否拼接Controller类上@ApiOperation注解的描述值
     *
     * @return 是否拼接Controller类上的描述值
     */
    boolean controllerApiValue() default true;

    /**
     * 是否记录方法的入参
     *
     * @return 是否记录方法的入参
     */
    boolean request() default true;

    /**
     * 若设置了 request = false、requestByError = true,则方法报错时,依然记录请求的入参
     *
     * @return 当 request = false时, 方法报错记录请求参数
     */
    boolean requestByError() default true;

    /**
     * 是否记录返回值
     *
     * @return 是否记录返回值
     */
    boolean response() default true;
}

1.2 saveAo

java 复制代码
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import java.io.Serializable;
import java.time.LocalDateTime;

/**
* 操作日志
* @TableName base_operation_log
*/
@Data
public class BaseOperationLogSaveAo implements Serializable {

    /**
    * 操作ID
    */
    @ApiModelProperty("操作ID")
    @Length(max= 50,message="编码长度不能超过50")
    private String requestIp;
    /**
    * 日志类型 1.操作类型 2.异常异常
    */
    @ApiModelProperty("日志类型 1.操作类型 2.异常异常")
    private LogTypeEnum type;
    /**
    * 操作人
    */
    @ApiModelProperty("操作人")
    @Length(max= 50,message="编码长度不能超过50")
    private String staffName;
    /**
    * 描述
    */
    @ApiModelProperty("描述")
    @Length(max= 255,message="编码长度不能超过255")
    private String description;
    /**
    * 业务ID
    */
    @ApiModelProperty("业务ID")
    private Long bizId;
    /**
    * 类路径
    */
    @ApiModelProperty("类路径")
    @Length(max= 255,message="编码长度不能超过255")
    private String classPath;
    /**
    * 请求方法
    */
    @ApiModelProperty("请求方法")
    @Length(max= 50,message="编码长度不能超过50")
    private String actionMethod;
    /**
    * 请求地址
    */
    @ApiModelProperty("请求地址")
    @Length(max= 255,message="编码长度不能超过255")
    private String requestUri;
    /**
    * 请求类型:GET:GET请求;POST:POST请求;PUT:PUT请求;DELETE:DELETE请求;PATCH:PATCH请求;TRACE:TRACE请求;HEAD:HEAD请求;OPTIONS:OPTIONS请求
    */
    @ApiModelProperty("请求类型:GET:GET请求;POST:POST请求;PUT:PUT请求;DELETE:DELETE请求;PATCH:PATCH请求;TRACE:TRACE请求;HEAD:HEAD请求;OPTIONS:OPTIONS请求")
    @Length(max= 10,message="编码长度不能超过10")
    private String httpMethod;
    /**
    * 参数
    */
    @ApiModelProperty("参数")
    private String params;
    /**
    * 返回值
    */
    @ApiModelProperty("返回值")
    private String result;
    /**
    * 异常描述
    */
    @ApiModelProperty("异常描述")
    private String exDetail;
    /**
    * 开始时间
    */
    @ApiModelProperty("开始时间")
    private LocalDateTime startTime;
    /**
    * 结束时间
    */
    @ApiModelProperty("结束时间")
    private LocalDateTime finishTime;
    /**
    * 消耗时间
    */
    @ApiModelProperty("消耗时间")
    private Long consumingTime;
    /**
    * 浏览器
    */
    @ApiModelProperty("浏览器")
    @Length(max= 500,message="编码长度不能超过500")
    private String ua;

    private Long createUserId;

}

13 enum

java 复制代码
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import com.ulinkle.common.core.interfaces.BaseEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

/**
 * 日志类型:1-操作;2-异常
 */
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "日志类型", description = "1-操作;2-异常")
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum LogTypeEnum implements BaseEnum<Integer> {
    /**
     *
     */
    OPT(1, "操作"),
    EX(2, "生产工单"),
    ;

    @EnumValue
    @JsonValue
    @ApiModelProperty(value = "code", allowableValues = "1,2,3", example = "1")
    private Integer code;

    @ApiModelProperty(value = "描述")
    private String desc;

    @Override
    public String getExtra() {
        return BaseEnum.super.getExtra();
    }

    @Override
    public boolean eq(Integer val) {
        return BaseEnum.super.eq(val);
    }

    @Override
    @JsonProperty
    public Integer getValue() {
        return BaseEnum.super.getValue();
    }

    public Long longValue() {
        return code.longValue();
    }

    @JsonCreator
    public static LogTypeEnum of(Integer val) {
        return Stream.of(values()).filter(item -> Objects.equals(item.getValue(), val)).findAny().orElse(null);
    }

    @Override
    public Set<String> getDescSet() {
        LogTypeEnum[] values = LogTypeEnum.values();
        Set<String> set = new HashSet<>();
        for (LogTypeEnum value : values) {
            set.add(value.getDesc());
        }
        return set;
    }
}

1.4 aspect

1.4.1

java 复制代码
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.ulinkle.common.core.annotation.WebLog;
import com.ulinkle.common.core.entity.BaseOperationLogSaveAo;
import com.ulinkle.common.core.entity.LogTypeEnum;
import com.ulinkle.common.core.utils.*;
import com.ulinkle.common.core.web.page.DataResponse;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.lang.NonNull;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * 操作日志使用spring event异步入库
 *
 * @author zuihou
 * @date 2019-07-01 15:15
 */
@Slf4j
@Aspect
public class LogAspect {

    public static final int MAX_LENGTH = 65535;
    private static final ThreadLocal<BaseOperationLogSaveAo> THREAD_LOCAL = new ThreadLocal<>();
    private static final String FORM_DATA_CONTENT_TYPE = "multipart/form-data";

    @Pointcut("execution(public * com.ulinkle.*.controller.*.*(..)) || @annotation(com.ulinkle.common.core.annotation.WebLog)")
    public void logAspect() {
    }

    /**
     * 返回通知
     *
     * @param ret       返回值
     * @param joinPoint 端点
     */
    @AfterReturning(returning = "ret", pointcut = "logAspect()")
    public void doAfterReturning(JoinPoint joinPoint, Object ret) {
        tryCatch(p -> {
//            WebLog log = getWebLog(joinPoint);
//            if (log == null) return;
            DataResponse<?> r = Convert.convert(DataResponse.class, ret);
            BaseOperationLogSaveAo logDTO = get();
            if (r == null) {
                logDTO.setType(LogTypeEnum.OPT);
                logDTO.setResult(getText(String.valueOf(ret == null ? StrPool.EMPTY : ret)));
            } else {
                if (r.isSuccess()) {
                    logDTO.setType(LogTypeEnum.OPT);
                } else {
                    logDTO.setType(LogTypeEnum.EX);
                    logDTO.setExDetail(r.getMsg());
                }
                logDTO.setResult(getText(r.toString()));
            }

            publishEvent(logDTO);
        });

    }

    /**
     * 异常通知
     *
     * @param joinPoint 端点
     * @param e         异常
     */
    @AfterThrowing(pointcut = "logAspect()", throwing = "e")
    public void doAfterThrowable(JoinPoint joinPoint, Throwable e) {
        tryCatch((aaa) -> {
//            WebLog log = getWebLog(joinPoint);
//            if (log == null) return;

            BaseOperationLogSaveAo optLogDTO = get();
            optLogDTO.setType(LogTypeEnum.EX);

            // 遇到错误时,请求参数若为空,则记录
            if (StrUtil.isEmpty(optLogDTO.getParams())) {
                Object[] args = joinPoint.getArgs();
                HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
                String strArgs = getArgs(args, request);
                optLogDTO.setParams(getText(strArgs));
            }

            // 异常对象
            optLogDTO.setExDetail(ExceptionUtil.stacktraceToString(e, MAX_LENGTH));

            publishEvent(optLogDTO);
        });
    }

    private WebLog getWebLog(JoinPoint joinPoint) {
        WebLog log = LogUtil.getTargetAnnotation(joinPoint);
        if (check(joinPoint, log)) {
            return null;
        }
        return log;
    }

    /**
     * 执行方法之前
     *
     * @param joinPoint 端点
     */
    @Before(value = "logAspect()")
    public void doBefore(JoinPoint joinPoint) {
        tryCatch(val -> {
//            WebLog log = getWebLog(joinPoint);
//            if (log == null) return;
            BaseOperationLogSaveAo optLogDTO = buildBaseOperationLogSaveAo(joinPoint, null);
            THREAD_LOCAL.set(optLogDTO);
        });
    }

    @NonNull
    private BaseOperationLogSaveAo buildBaseOperationLogSaveAo(JoinPoint joinPoint, WebLog log) {
        // 开始时间
        BaseOperationLogSaveAo optLogDTO = get();
        optLogDTO.setCreateUserId(SecurityUtils.getLoginId().longValue());
        optLogDTO.setStaffName(SecurityUtils.getLoginName());
        setDescription(joinPoint, log, optLogDTO);
        // 类名
        optLogDTO.setClassPath(joinPoint.getTarget().getClass().getName());
        //获取执行的方法名
        optLogDTO.setActionMethod(joinPoint.getSignature().getName());

        HttpServletRequest request = setParams(joinPoint, log, optLogDTO);
        optLogDTO.setRequestIp(getClientIPByHeader(request));
        optLogDTO.setRequestUri(URLUtil.getPath(request.getRequestURI()));
        optLogDTO.setHttpMethod(request.getMethod());
        optLogDTO.setUa(StrUtil.sub(request.getHeader("user-agent"), 0, 500));
        optLogDTO.setStartTime(LocalDateTime.now());
        return optLogDTO;
    }

    public static String getClientIPByHeader(HttpServletRequest request, String... headerNames) {
        String ip;
        for (String header : headerNames) {
            ip = request.getHeader(header);
            if (!NetUtil.isUnknown(ip)) {
                return NetUtil.getMultistageReverseProxyIp(ip);
            }
        }

        ip = request.getRemoteAddr();
        return NetUtil.getMultistageReverseProxyIp(ip);
    }

    @NonNull
    private HttpServletRequest setParams(JoinPoint joinPoint, WebLog log, BaseOperationLogSaveAo optLogDTO) {
        // 参数
        Object[] args = joinPoint.getArgs();

        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes(), "只能在Spring Web环境使用@WebLog记录日志")).getRequest();
//        if (log.request()) {
            String strArgs = getArgs(args, request);
            optLogDTO.setParams(getText(strArgs));
//        }
        return request;
    }

    private void setDescription(JoinPoint joinPoint, WebLog log, BaseOperationLogSaveAo optLogDTO) {
        String controllerDescription = "";
        Api api = joinPoint.getTarget().getClass().getAnnotation(Api.class);
        if (api != null) {
            String[] tags = api.tags();
            if (ArrayUtil.isNotEmpty(tags)) {
                controllerDescription = tags[0];
            }
        }
        String  controllerMethodDescription = LogUtil.getApiOperationValue(joinPoint);
//        String controllerMethodDescription = LogUtil.getDescribe(log);
//        if (StringUtils.isEmpty(controllerMethodDescription)) {
//            controllerMethodDescription = LogUtil.getApiOperationValue(joinPoint);
//        }
//        if (log.controllerApiValue()) {
//            optLogDTO.setDescription(controllerDescription + "-" + controllerMethodDescription);
//        } else {
//            optLogDTO.setDescription(controllerMethodDescription);
//        }
        optLogDTO.setDescription(controllerDescription + "-" + controllerMethodDescription);
    }


    private BaseOperationLogSaveAo get() {
        BaseOperationLogSaveAo log = THREAD_LOCAL.get();
        if (log == null) {
            return new BaseOperationLogSaveAo();
        }
        return log;
    }

    private void tryCatch(Consumer<String> consumer) {
        try {
            consumer.accept("");
        } catch (Exception e) {
            log.warn("记录操作日志异常", e);
            THREAD_LOCAL.remove();
        }
    }

    private void publishEvent(BaseOperationLogSaveAo log) {
        log.setFinishTime(LocalDateTime.now());
        log.setConsumingTime(log.getStartTime().until(log.getFinishTime(), ChronoUnit.MILLIS));
        MySpringUtils.publishEvent(new LogEvent(log));
        THREAD_LOCAL.remove();
    }

    /**
     * 监测是否需要记录日志
     *
     * @param joinPoint 端点
     * @param log       操作日志
     * @return true 表示不需要记录日志
     */
    private boolean check(JoinPoint joinPoint, WebLog log) {
        if (log == null || !log.enabled()) {
            return true;
        }
        // 读取目标类上的注解
        WebLog targetClass = joinPoint.getTarget().getClass().getAnnotation(WebLog.class);
        // 加上 log == null 会导致父类上的方法永远需要记录日志
        return targetClass != null && targetClass.enabled();
    }

    /**
     * 截取指定长度的字符串
     *
     * @param val 参数
     * @return 截取文本
     */
    private String getText(String val) {
        return StrUtil.sub(val, 0, 65535);
    }

    private String getArgs(Object[] args, HttpServletRequest request) {
        String strArgs = StrPool.EMPTY;
        Object[] params = Arrays.stream(args).filter(item -> !(item instanceof ServletRequest || item instanceof ServletResponse)).toArray();

        try {
            if (!request.getContentType().contains(FORM_DATA_CONTENT_TYPE)) {
                strArgs = JsonUtil.toJson(params);
            }
        } catch (Exception e) {
            try {
                strArgs = Arrays.toString(params);
            } catch (Exception ex) {
                log.warn("解析参数异常", ex);
            }
        }
        return strArgs;
    }


}

1.4.2

java 复制代码
import com.ulinkle.common.core.entity.BaseOperationLogSaveAo;
import org.springframework.context.ApplicationEvent;

/**
 * @author 王
 * @description
 * @createDate
 */
public class LogEvent extends ApplicationEvent {
    public LogEvent(BaseOperationLogSaveAo logSaveAo) {
        super(logSaveAo);
    }
}

1.4.3

java 复制代码
import com.ulinkle.common.core.entity.BaseOperationLogSaveAo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;

import java.util.function.Consumer;

/**
 * @author 王
 * @description
 * @createDate
 */
@Slf4j
@AllArgsConstructor
public class LogListener {
    private final Consumer<BaseOperationLogSaveAo> consumer;

    @Async
    @Order
    @EventListener(LogEvent.class)
    public void saveSysLog(LogEvent event) {
        BaseOperationLogSaveAo sysLog = (BaseOperationLogSaveAo) event.getSource();
        consumer.accept(sysLog);
    }
}

1.5 util

java 复制代码
import com.ulinkle.common.core.annotation.WebLog;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;

/**
 * 日志工具类
 *
 * @author zuihou
 * @date 2019-04-28 11:30
 */
@Slf4j
public final class LogUtil {
    private LogUtil() {
    }

    /***
     * 获取操作信息
     */
    public static String getDescribe(JoinPoint point) {
        WebLog annotation = getTargetAnnotation(point);
        if (annotation == null) {
            return StrPool.EMPTY;
        }
        return annotation.value();
    }

    public static String getDescribe(WebLog annotation) {
        if (annotation == null) {
            return StrPool.EMPTY;
        }
        return annotation.value();
    }

    /**
     * 优先从子类获取 @WebLog:
     * 1,若子类重写了该方法,有标记就记录日志,没标记就忽略日志
     * 2,若子类没有重写该方法,就从父类获取,父类有标记就记录日志,没标记就忽略日志
     */
    public static WebLog getTargetAnnotation(JoinPoint point) {
        try {
            WebLog annotation = null;
            if (point.getSignature() instanceof MethodSignature) {
                Method method = ((MethodSignature) point.getSignature()).getMethod();
                if (method != null) {
                    annotation = method.getAnnotation(WebLog.class);
                }
            }
            return annotation;
        } catch (Exception e) {
            log.warn("获取 {}.{} 的 @WebLog 注解失败", point.getSignature().getDeclaringTypeName(), point.getSignature().getName(), e);
            return null;
        }
    }

    public static String getApiOperationValue(JoinPoint point) {
        try {
            if (point.getSignature() instanceof MethodSignature) {
                Method method = ((MethodSignature) point.getSignature()).getMethod();
                if (method != null) {
                    ApiOperation annotation = method.getAnnotation(ApiOperation.class);
                    if (annotation == null) {
                        return "";
                    }
                    return annotation.value();
                }
            }
            return "";
        } catch (Exception e) {
            log.warn("获取 {}.{} 的 @ApiOperation 注解失败", point.getSignature().getDeclaringTypeName(), point.getSignature().getName(), e);
            return null;
        }
    }

}

1.5.2

java 复制代码
import lombok.Getter;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;

import java.util.Map;

/**
 * Spring工具类
 */
public final class MySpringUtils {
    @Getter
    private static ApplicationContext applicationContext;
    private static ApplicationContext parentApplicationContext;

    private MySpringUtils() {
    }

    /**
     * 单例Holder模式: 优点:将懒加载和线程安全完美结合的一种方式(无锁)。(推荐)
     *
     * @return 实实例
     */
    public static MySpringUtils getInstance() {
        return SpringUtilsHolder.INSTANCE;
    }

    public static void setApplicationContext(ApplicationContext ctx) {
        Assert.notNull(ctx, "SpringUtil injection ApplicationContext is null");
        applicationContext = ctx;
        parentApplicationContext = ctx.getParent();
    }

    public static Object getBean(String name) {
        Assert.hasText(name, "SpringUtil name is null or empty");
        try {
            return applicationContext.getBean(name);
        } catch (Exception e) {
            return parentApplicationContext.getBean(name);
        }
    }

    public static <T> T getBean(String name, Class<T> type) {
        Assert.hasText(name, "SpringUtil name is null or empty");
        Assert.notNull(type, "SpringUtil type is null");
        try {
            return applicationContext.getBean(name, type);
        } catch (Exception e) {
            return parentApplicationContext.getBean(name, type);
        }
    }

    public static <T> T getBean(Class<T> type) {
        Assert.notNull(type, "SpringUtil type is null");
        try {
            return applicationContext.getBean(type);
        } catch (Exception e) {
            return parentApplicationContext.getBean(type);
        }
    }

    public static <T> Map<String, T> getBeansOfType(Class<T> type) {
        Assert.notNull(type, "SpringUtil type is null");
        try {
            return applicationContext.getBeansOfType(type);
        } catch (Exception e) {
            return parentApplicationContext.getBeansOfType(type);
        }
    }

    public static ApplicationContext publishEvent(Object event) {
        applicationContext.publishEvent(event);
        return applicationContext;
    }


    /**
     * <p>
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载
     */
    private static class SpringUtilsHolder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static final MySpringUtils INSTANCE = new MySpringUtils();
    }

}

三.服务配置

需要记录日志的服务增加以下配置

java 复制代码
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ulinkle.common.api.base.BaseOperateLogFeignClient;
import com.ulinkle.common.core.log.LogAspect;
import com.ulinkle.common.core.log.LogListener;
import com.ulinkle.common.core.utils.MySpringUtils;
import com.ulinkle.pos.api.ItemLogService;
import com.ulinkle.pos.event.ItemLogListener;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import java.util.List;

/***
 *@description
 *@author wangShu
 *@version 1.0.0
 *@create 2023/12/6 11:16
 **/
@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.stream().filter(c -> c instanceof MappingJackson2HttpMessageConverter)
                .map(c ->(MappingJackson2HttpMessageConverter)c)
                .forEach(c->{
                    ObjectMapper mapper = c.getObjectMapper();
                    // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
                    mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
                    c.setObjectMapper(mapper);
                });
    }

    @Bean
    public ItemLogListener itemLogListener(ItemLogService itemLogService) {
        return new ItemLogListener(itemLogService::saveBatch);
    }

    @Bean
    public LogListener sysLogListener(BaseOperateLogFeignClient logApi) {
        return new LogListener(logApi::add);
    }

    @Bean
    public LogAspect getLogAspect() {
        return new LogAspect();
    }


    /**
     * Spring 工具类
     *
     * @param applicationContext 上下文
     */
    @Bean
    public MySpringUtils getMySpringUtils(ApplicationContext applicationContext) {
        MySpringUtils instance = MySpringUtils.getInstance();
        MySpringUtils.setApplicationContext(applicationContext);
        return instance;
    }
}
相关推荐
ada7_1 小时前
LeetCode(python)——148.排序链表
python·算法·leetcode·链表
码界奇点1 小时前
Java Web学习 第15篇jQuery从入门到精通的万字深度解析
java·前端·学习·jquery
雨落秋垣1 小时前
手搓 Java 的用户行为跟踪系统
java·开发语言·linq
盖世英雄酱581361 小时前
java深度调试技术【第六七八章:宽字节与多字节】
java·后端
岁月宁静2 小时前
LangChain + LangGraph 实战:构建生产级多模态 WorkflowAgent 的完整指南
人工智能·python·agent
爱丽_2 小时前
深入理解 Java Socket 编程与线程池:从阻塞 I/O 到高并发处理
java·开发语言
济南壹软网络科技有限公司2 小时前
云脉IM的高性能消息路由与离线推送机制摘要:消息的“零丢失、低延迟”之道
java·即时通讯源码·开源im·企业im
Seven972 小时前
剑指offer-46、孩⼦们的游戏(圆圈中最后剩下的数)
java
serendipity_hky2 小时前
互联网大厂Java面试故事:核心技术栈与场景化业务问题实战解析
java·spring boot·redis·elasticsearch·微服务·消息队列·内容社区