接口日志存储

1. 建接口日志表

CREATE TABLE `sys_operate_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  `title` varchar(50) DEFAULT '' COMMENT '模块标题',
  `business_type` int(2) DEFAULT '4' COMMENT '业务类型(0查询 1新增 2修改 3删除 4其他)',
  `method` varchar(100) DEFAULT '' COMMENT '方法名称',
  `resp_time` bigint(20) DEFAULT NULL COMMENT '响应时间',
  `request_method` varchar(10) DEFAULT '' COMMENT '请求方式',
  `browser` varchar(255) DEFAULT NULL COMMENT '浏览器类型',
  `operate_type` int(1) DEFAULT '3' COMMENT '操作类别(0网站用户 1后台用户 2小程序 3其他)',
  `operate_url` varchar(255) DEFAULT '' COMMENT '请求URL',
  `operate_ip` varchar(128) DEFAULT '' COMMENT '主机地址',
  `operate_location` varchar(255) DEFAULT '' COMMENT '操作地点',
  `operate_param` text COMMENT '请求参数',
  `json_result` text COMMENT '返回参数',
  `status` int(1) DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
  `error_msg` text COMMENT '错误消息',
  `create_id` bigint(20) DEFAULT NULL COMMENT '操作人id',
  `create_name` varchar(50) DEFAULT '' COMMENT '操作人员',
  `create_time` datetime DEFAULT NULL COMMENT '操作时间',
  `update_id` bigint(20) NULL DEFAULT NULL COMMENT '更新人id',
  `update_name` varchar(64) NULL DEFAULT '' COMMENT '更新者',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统管理-操作日志记录';

2.  实体类,也可以MybatisPlus生成

import com.baomidou.mybatisplus.annotation.TableName;
import com.maple.demo.config.bean.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@TableName("sys_operate_log")
@ApiModel(value = "OperateLog对象", description = "系统管理-操作日志记录")
public class OperateLog extends BaseEntity {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("模块标题")
    private String title;

    @ApiModelProperty("业务类型(0查询 1新增 2修改 3删除 4其他)")
    private Integer businessType;

    @ApiModelProperty("方法名称")
    private String method;

    @ApiModelProperty("响应时间")
    private Long respTime;

    @ApiModelProperty("请求方式")
    private String requestMethod;

    @ApiModelProperty("浏览器类型")
    private String browser;

    @ApiModelProperty("操作类别(0网站用户 1后台用户 2小程序 3其他)")
    private Integer operateType;

    @ApiModelProperty("请求URL")
    private String operateUrl;

    @ApiModelProperty("主机地址")
    private String operateIp;

    @ApiModelProperty("操作地点")
    private String operateLocation;

    @ApiModelProperty("请求参数")
    private String operateParam;

    @ApiModelProperty("返回参数")
    private String jsonResult;

    @ApiModelProperty("操作状态(0正常 1异常)")
    private Integer status;

    @ApiModelProperty("错误消息")
    private String errorMsg;
}

3. 业务类型枚举类 BusinessTypeEnum

public enum BusinessTypeEnum {
    // 0查询 1新增 2修改 3删除 4其他
    SELECT,
    INSERT,
    UPDATE,
    DELETE,
    OTHER
}

4. 操作用户类型枚举类 OperateTypeEnum

public enum OperateTypeEnum {
    // 0网站用户 1后台用户 2小程序 3其他
    BLOG,
    ADMIN,
    APP,
    OTHER
}

5. 配置自定义注解,为需要生成日志的接口添加注解

import com.maple.common.enums.BusinessTypeEnum;
import com.maple.common.enums.OperateTypeEnum;

import java.lang.annotation.*;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MapleLog {

    // 0网站用户 1后台用户 2小程序 3其他
    OperateTypeEnum operateType() default OperateTypeEnum.OTHER;

    // 0查询 1新增 2修改 3删除 4其他
    BusinessTypeEnum businessType() default BusinessTypeEnum.SELECT;
    
    // 返回保存结果是否落库,没用的大结果可以不记录,比如分页查询等等,设为false即可
    boolean saveResult() default true;
}

6. AOP功能实现

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.yang.demo.common.model.MapleLog;
import com.yang.demo.config.bean.GlobalConfig;
import com.yang.demo.entity.OperateLog;
import com.yang.demo.mapper.OperateLogMapper;
import com.yang.demo.util.JwtUtil;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;

@Aspect
@Component
@Slf4j
@AllArgsConstructor
public class SystemLogAspect {

    private final OperateLogMapper operateLogMapper;

    // 配置切面,使用注解配置
    @Pointcut(value = "@annotation(com.yang.demo.common.model.MapleLog)")
    public void systemLog() {
        // nothing
    }

    @Around(value = "systemLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        int maxTextLength = 65000;
        Object obj;
        // 定义执行开始时间
        long startTime;
        // 定义执行结束时间
        long endTime;
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        // 取swagger的描述信息
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        MapleLog mapleLog = method.getAnnotation(MapleLog.class);
        OperateLog operateLog = new OperateLog();

        try {
            operateLog.setBrowser(request.getHeader("USER-AGENT"));
            operateLog.setOperateUrl(request.getRequestURI());
            operateLog.setRequestMethod(request.getMethod());
            operateLog.setMethod(String.valueOf(joinPoint.getSignature()));
            operateLog.setCreateTime(new Date());
            operateLog.setOperateIp(getIpAddress(request));
            // 取JWT的登录信息,无需登录可以忽略
            if (request.getHeader(GlobalConfig.TOKEN_NAME) != null) {
                operateLog.setCreateName(JwtUtil.getAccount());
                operateLog.setCreateId(JwtUtil.getUserId());
            }
            String operateParam = JSON.toJSONStringWithDateFormat(joinPoint.getArgs(), "yyyy-MM-dd HH:mm:ss", SerializerFeature.WriteMapNullValue);
            if (operateParam.length() > maxTextLength) {
                operateParam = operateParam.substring(0, maxTextLength);
            }
            operateLog.setOperateParam(operateParam);

            if (apiOperation != null) {
                operateLog.setTitle(apiOperation.value() + "");
            }

            if (mapleLog != null) {
                operateLog.setBusinessType(mapleLog.businessType().ordinal());
                operateLog.setOperateType(mapleLog.operateType().ordinal());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        startTime = System.currentTimeMillis();
        try {
            // 执行切面方法
            obj = joinPoint.proceed();
            operateLog.setStatus(0);
            // 判断是否保存返回结果,列表页可以设为false
            if (Objects.nonNull(mapleLog) && mapleLog.saveResult()) {
                String result = JSON.toJSONString(obj);
                if (result.length() > maxTextLength) {
                    result = result.substring(0, maxTextLength);
                }
                operateLog.setJsonResult(result);
            }
        } catch (Exception e) {
            // 记录异常信息
            operateLog.setStatus(1);
            operateLog.setErrorMsg(e.toString());
            throw e;
        } finally {
            endTime = System.currentTimeMillis();
            operateLog.setRespTime(endTime - startTime);
            operateLogMapper.insert(operateLog);
        }
        return obj;
    }

    /**
     * 获取Ip地址
     */
    private static String getIpAddress(HttpServletRequest request) {
        String xip = request.getHeader("X-Real-IP");
        String xFor = request.getHeader("X-Forwarded-For");
        String unknown = "unknown";
        if (StringUtils.isNotEmpty(xFor) && !unknown.equalsIgnoreCase(xFor)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = xFor.indexOf(",");
            if (index != -1) {
                return xFor.substring(0, index);
            } else {
                return xFor;
            }
        }
        xFor = xip;
        if (StringUtils.isNotEmpty(xFor) && !unknown.equalsIgnoreCase(xFor)) {
            return xFor;
        }
        if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
            xFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
            xFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
            xFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
            xFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(xFor) || unknown.equalsIgnoreCase(xFor)) {
            xFor = request.getRemoteAddr();
        }
        return xFor;
    }
}

7. 测试类

import com.yang.demo.common.model.MapleLog;
import com.yang.demo.config.bean.ExceptionCode;
import com.yang.demo.common.enums.BusinessTypeEnum;
import com.yang.demo.common.enums.OperateTypeEnum;
import com.yang.demo.exception.CommonException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/example")
@Api(tags = "实例演示-日志记录演示接口")
public class TestSystemLogController {

    @ApiOperation(value = "测试带参数、有返回结果的get请求")
    @GetMapping("/testGetLog/{id}")
    @MapleLog(businessType = BusinessTypeEnum.OTHER, operateType = OperateTypeEnum.OTHER)
    public Test testGetLog(@PathVariable Integer id) {
        Test test = new Test();
        test.setName("笑小枫");
        test.setAge(18);
        test.setRemark("大家好,我是笑小枫,喜欢我的小伙伴点个赞呗");
        return test;
    }

    @ApiOperation(value = "测试json参数、抛出异常的post请求")
    @PostMapping("/testPostLog")
    @MapleLog(businessType = BusinessTypeEnum.OTHER, operateType = OperateTypeEnum.OTHER, saveResult = false)
    public Test testPostLog(@RequestBody Test param) {
        Test test = new Test();
        test.setName("笑小枫");
        if (test.getAge() == null) {
            // 这里使用了自定义异常,测试可以直接抛出RuntimeException
            throw new CommonException(ExceptionCode.COMMON_ERROR);
        }
        test.setRemark("大家好,我是笑小枫,喜欢我的小伙伴点个赞呗");
        return test;
    }

    @Data
    static class Test {
        private String name;

        private Integer age;

        private String remark;
    }
}

8. 测试

http://localhost:6666/example/testGetLog/1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值