SpringBoot自定义注解实现接口日志记录

主要实现是通过ApiLog记录接口请求的所有日志,然后将日志对象先记录到redis的list中,然后通过动态定时器定时将redis中的日志对象写入到数据库中。

一、自定义注解

自定义注解ApiLog,主要包含三个参数:

第一个参数为接口操作类型,在我的项目中分为两种,一种是登录认证类,一种是接口请求类

第二个参数为操作描述,可以在使用自定义注解时手动输入

第三个参数为模块名称

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiLog {
  /**
  	public final static int AUTH_LOGIN = 0;
   	public final static int APPLY_RES = 1; **/
    int type();

    String actionDescrible();

    String module();
}

二、注解逻辑类

逻辑主要分为三块:

第一块:beforeLog,主要记录接口请求的一些基本参数,如接口地址,请求方式,请求参数等等

第二块:AfterReturning,主要记录接口正常返回的日志

第三块:AfterThrowing,主要记录接口异常时返回的日志报错信息

@Component
@Aspect
@Order(1)
@Slf4j
public class ApiLogAspect {
    private static final ThreadLocal<BasicLogEntity> threadLocalBasicLog = new ThreadLocal();
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    long startTime;
    long endTime;

    @Autowired
    UserMapper userMapper;


    @Autowired
    RedisClient redis;

    public ApiLogAspect() {
    }

    @Before("@annotation(apiLog)")
    public void beforeLog(JoinPoint jp,ApiLog apiLog){
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        MethodSignature sign = (MethodSignature) jp.getSignature();
        Method method = sign.getMethod();
        BasicLogEntity basicLogEntity = new BasicLogEntity();
        this.fillBasicLog(basicLogEntity,jp,request,apiLog,method);
        threadLocalBasicLog.set(basicLogEntity);
        startTime = System.currentTimeMillis();
    }

    @AfterThrowing(
            value = "@annotation(api)",
            throwing = "ex"
    )
    public void exceptionLog(ApiLog api, Exception ex) {
        BasicLogEntity basicLog = (BasicLogEntity) threadLocalBasicLog.get();
        UserEntity userEntity=new UserEntity();
        if(StringUtils.isBlank(basicLog.idCard)){
            if(StpUtil.isLogin()){
                String idcard= StpUtil.getLoginIdAsString();
                if(StringUtils.isBlank(idcard)){
                    userEntity.setUsername("未登录");
                    userEntity.setIdCard("未登录");
                }else{
                    userEntity.setIdCard(idcard);
                    userEntity=userMapper.selectOne(userEntity);
                    if(userEntity==null){
                        userEntity.setUsername("未登录");
                        userEntity.setIdCard("未登录");
                    }
                }
            }else {
                userEntity.setUsername("未登录");
                userEntity.setIdCard("未登录");
            }
            basicLog.username=userEntity.getUsername();
            basicLog.idCard=userEntity.getIdCard();
        }
        basicLog.success = 0;
        String msg = ex.toString();
        basicLog.exceptionMsg = msg.length() <= 128 ? msg : msg.substring(0, 128);
        this.redis.listPush("savelog", basicLog);
    }

    @AfterReturning(
            value = "@annotation(api)",
            returning = "res"
    )
    public void returnLog(ApiLog api, Result res) {
        BasicLogEntity basicLog = (BasicLogEntity) threadLocalBasicLog.get();
        UserEntity userEntity=new UserEntity();
        if(StringUtils.isBlank(basicLog.idCard)){
            if(StpUtil.isLogin()){
                String idcard= StpUtil.getLoginIdAsString();
                if(StringUtils.isBlank(idcard)){
                    userEntity.setUsername("未登录");
                    userEntity.setIdCard("未登录");
                }else{
                    userEntity.setIdCard(idcard);
                    userEntity=userMapper.selectOne(userEntity);
                    if(userEntity==null){
                        userEntity.setUsername("未登录");
                        userEntity.setIdCard("未登录");
                    }
                }
            }else {
                userEntity.setUsername("未登录");
                userEntity.setIdCard("未登录");
            }
            basicLog.username=userEntity.getUsername();
            basicLog.idCard=userEntity.getIdCard();
        }
        basicLog.notice = res.getMsg();
        basicLog.success = 1;

        this.redis.listPush("savelog", basicLog);
    }


    private void fillBasicLog(BasicLogEntity basicLogEntity, JoinPoint jp, HttpServletRequest request, ApiLog log, Method method) {
        if(StpUtil.isLogin()){
            String idcard= StpUtil.getLoginIdAsString();
            if(StringUtils.isNotBlank(idcard)) {
                UserEntity userEntity=new UserEntity();
                userEntity.setIdCard(idcard);
                userEntity=userMapper.selectOne(userEntity);
                if (userEntity != null) {
                    basicLogEntity.setIdCard(idcard);
                    basicLogEntity.setUsername(userEntity.getUsername());
                }

            }
        }
        basicLogEntity.setUserIp(IpUtil.getIpAddr(request));
        basicLogEntity.setRequestApi(request.getRequestURI());
        basicLogEntity.setRequestMethod(request.getMethod());
        basicLogEntity.setMethodName(method.getName());
        basicLogEntity.setActionDescribe(log.actionDescrible());
        basicLogEntity.setModule(log.module());
        basicLogEntity.setLogType(log.type());
        basicLogEntity.setClassName(method.getDeclaringClass().getSimpleName());
        basicLogEntity.setCreateTime(new Date());
        String args = Arrays.toString(jp.getArgs());
        basicLogEntity.setRequestArgs(args.length()<=64?args:args.substring(0,64));
    }
}

三、日志实体类

@Data
@Component
@Table(name = "xxxxxxxxxxxx")
public class BasicLogEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    public Integer accountId;
    public String idCard;
    public String username;
    public String userIp;
    public String actionDescribe;
    public String module;
    public String requestApi;
    public String requestMethod;
    public String className;
    public String methodName;
    public String requestArgs;
    public String exceptionMsg;
    public String notice;
    public short success;
    public Date createTime;
    public int logType;
}

四、动态定时器定时将redis中的日志同步到数据库中

@Configuration
@EnableScheduling
@EnableAsync
public class LogDeal implements SchedulingConfigurer {
    private static final String SAVE_CONSTANT = "savelog";
    @Autowired
    public RedisClient redis;
    @Autowired
    public LogMapper logMapper;

    public LogDeal() {
    }

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        taskRegistrar.addTriggerTask(() -> {
            Boolean res = true;

            while(res) {
                BasicLogEntity basicLog = (BasicLogEntity) this.redis.listPop("savelog");
                if (basicLog != null) {
                    this.logMapper.saveLog(basicLog);
                } else {
                    res = false;
                }
            }

        }, (triggerContext) -> {
            String cron = "0 0/1 * * * ?";
            if (!StringUtils.isEmpty(Constant.logsavetime)) {
                cron = Constant.logsavetime;
            }

            return (new CronTrigger(cron)).nextExecutionTime(triggerContext);
        });
    }
}

五、数据入库Mapper文件


@Mapper
@Component
public interface LogMapper {
    @SelectProvider(
            type = LogProvider.class,
            method = "getTable"
    )
    String getTable(String tablename);

    @SelectProvider(
            type = LogProvider.class,
            method = "createTable"
    )
    Boolean createTable(String tablename);

    @SelectProvider(
            type = LogProvider.class,
            method = "getLog"
    )
    List<BasicLogEntity> getList(Integer type);

    @InsertProvider(
            type = LogProvider.class,
            method = "saveLog"
    )
    Boolean saveLog(BasicLogEntity log);

    public static class LogProvider {
        public LogProvider() {
        }

        public String getLog(Integer type) {
            String sql = "select * from ";
            String[] tables = Constant.logtable.split(",");
            sql = sql + tables[type];
            return sql + "order by id desc";
        }

        public String saveLog(BasicLogEntity log) {
            String sql = "insert into ";
            String[] tables = Constant.logtable.split(",");
            sql = sql + tables[log.logType];
            return sql + "(id_card,username,user_ip,action_describe,module,request_api,request_method,class_name,method_name,request_args," + "exception_msg,notice,success,create_time) values(#{idCard},#{username},#{userIp},#{actionDescribe},#{module},#{requestApi}," + "#{requestMethod},#{className},#{methodName},#{requestArgs},#{exceptionMsg},#{notice},#{success},#{createTime})";
        }

        public String getTable(String tablename) {
            String sql = "show tables like '" + tablename + "'";
            return sql;
        }

        public String createTable(String tablename) {
            String sql = "CREATE TABLE " + tablename + "(\r\n" + "  `id` bigint(20) NOT NULL AUTO_INCREMENT,\r\n" + "  `account_id` bigint(20) DEFAULT NULL,\r\n" + "  `username` varchar(32) DEFAULT NULL,\r\n" + "  `user_ip` varchar(32) DEFAULT NULL,\r\n" + "  `action_describe` varchar(32) DEFAULT NULL,\r\n" + "  `request_api` varchar(32) DEFAULT NULL,\r\n" + "  `request_method` varchar(32) DEFAULT NULL,\r\n" + "  `class_name` varchar(32) DEFAULT NULL,\r\n" + "  `method_name` varchar(32) DEFAULT NULL,\r\n" + "  `request_args` varchar(64) DEFAULT NULL,\r\n" + "  `exception_msg` varchar(128) DEFAULT NULL,\r\n" + "  `notice` varchar(64) DEFAULT NULL,\r\n" + "  `success` tinyint(1) DEFAULT NULL,\r\n" + "  `createtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\r\n" + "  PRIMARY KEY (`id`)\r\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='xxxxxxxxx'";
            return sql;
        }
    }
}

六、其他(读取配置文件参数工具类、IPUtil获取接口请求IP、配置文件参数映射)

public class Constant {
    public static final int need_login = 402;
    public static final int no_login = Integer.parseInt((String)BaseUtil.getProMap().get("no_login"));
    public static final int token_expired = Integer.parseInt((String)BaseUtil.getProMap().get("token_expired"));
    public static final int auth_fail = Integer.parseInt((String)BaseUtil.getProMap().get("auth_fail"));
    public static final int no_permi = Integer.parseInt((String)BaseUtil.getProMap().get("no_permi"));
    public static final String jump = (String)BaseUtil.getProMap().get("jump");
    public static final String jumpurl = (String)BaseUtil.getProMap().get("jumpurl");
    public static final String type = (String)BaseUtil.getProMap().get("type");
    public static final String origin = (String)BaseUtil.getProMap().get("origin");
    public static final String method = (String)BaseUtil.getProMap().get("method");
    public static final String max_age = (String)BaseUtil.getProMap().get("max_age");
    public static final String header = (String)BaseUtil.getProMap().get("header");
    public static final String logsavetime = (String)BaseUtil.getProMap().get("logsavetime");
    public static final String logtable = (String)BaseUtil.getProMap().get("logtable");
    public static final String headername = (String)BaseUtil.getProMap().get("headername");
    public static final String jwtkey = (String)BaseUtil.getProMap().get("jwtkey");
    public static final String session_id = (String)BaseUtil.getProMap().get("session_id");
    public static final String session_timeout = (String)BaseUtil.getProMap().get("session_timeout");
    public static final String cook_path = (String)BaseUtil.getProMap().get("cook_path");
    public static final String webpath = (String)BaseUtil.getProMap().get("webpath");
    public static final String dirpath = (String)BaseUtil.getProMap().get("dirpath");
    public static final String http_path = (String)BaseUtil.getProMap().get("http_path");
    public static final String valicodemin = (String)BaseUtil.getProMap().get("valicodemin");
    public static final String valicodemax = (String)BaseUtil.getProMap().get("valicodemax");
    public static final String valicodewidth = (String)BaseUtil.getProMap().get("valicodewidth");
    public static final String valicodebit = (String)BaseUtil.getProMap().get("valicodebit");
    public static final String redistopic = (String)BaseUtil.getProMap().get("redistopic");
    public static final String certpassword = (String)BaseUtil.getProMap().get("certpassword");
    public static final String certpath = (String)BaseUtil.getProMap().get("certpath");

    public Constant() {
    }
}
public class IpUtil {
    public IpUtil() {
    }

    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
                if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                    ip = request.getRemoteAddr();
                }
            }
        }
        return ip;
    }
}
public class BaseUtil {
    private static BaseUtil baseUtil = null;
    private static Map<String, String> proMap = new HashMap();

    private BaseUtil() {
        Properties pro = new Properties();
        InputStream iso = this.getClass().getClassLoader().getResourceAsStream("base.properties");

        try {
            pro.load(iso);
            Enumeration e = pro.propertyNames();

            while(e.hasMoreElements()) {
                String key = (String)e.nextElement();
                String value = (String)pro.get(key);
                proMap.put(key, value);
            }
        } catch (IOException var14) {
            var14.printStackTrace();
        } finally {
            try {
                iso.close();
            } catch (IOException var13) {
                var13.printStackTrace();
            }

        }

    }

    public static Map<String, String> getProMap() {
        if (baseUtil == null) {
            baseUtil = new BaseUtil();
        }

        return proMap;
    }
}
#base.properties配置文件

#基础权限
#token过期
token_expired=401
#没有登录
no_login=402
#认证失败
auth_fail=403
#无权限
no_permi=404

#跨域配置
#type==true 允许所有跨域   type==false 只允许配置中的来源
type=true
#允许通过的源  type==0 必须配置,号分隔
origin=
#许通过的请求方法,号分隔
method=POST, GET, OPTIONS, DELETE, PUT, PATCH
#多久免检
max_age=36000
#允许通过的请求头
header=X-Opt-Dept-Id,Content-Type,x-requested-with,token

#日志配置 认证登录日志  申请资源日志  释放资源日志  工单审核日志  权限分配日志  ---表名 对应下表 0 1 2 3 4 5
#logsavetime=0 0/10 * * * ?
logsavetime=0 0/1 * * * ?
logtable=t_log_auth_login,t_log_apply_res,t_log_release_res,t_log_order_review,t_log_allocation_rol


#session配置
session_id=sessionId
#session 失效时间单位是秒
session_timeout=10800
cook_path=/
  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot中可以自定义注解实现特定的功能。自定义注解的步骤如下: 1. 使用`@interface`关键字来定义注解,可以在注解中设置属性。 2. 可以通过注解的属性来传递参数,比如设置注解中的属性值。 3. 可以通过判断某个类是否有特定注解来进行相应的操作。 在SpringBoot中,自定义注解可以用于实现日志记录、定时器等功能。通过使用注解,可以简化代码,并提高开发效率。同时,自定义注解也是Spring框架中广泛应用的一种方式,可以在SpringMVC框架中使用注解来配置各种功能。而在SpringBoot框架中,更是将注解的使用推向了极致,几乎将传统的XML配置都替换为了注解。因此,对于SpringBoot来说,自定义注解是非常重要的一部分。123 #### 引用[.reference_title] - *1* *3* [springboot 自定义注解(含源码)](https://blog.csdn.net/yb546822612/article/details/88116654)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *2* [SpringBoot-自定义注解](https://blog.csdn.net/weixin_44809337/article/details/124366325)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值