SpringBoot 自定义注解实现操作日志记录

news2025/7/18 6:22:33

文章目录

  • 前言
  • 正文
    • 一、项目结构介绍
    • 二、核心类
    • 2.1 核心注解
      • 2.1.1 CLog 日志注解
      • 2.1.2 ProcessorBean 处理器bean
    • 2.2 切面类
    • 2.3 自定义线程池
    • 2.4 工具类
      • 2.4.1 管理者工具类
    • 2.5 测试
      • 2.5.1 订单创建处理器
      • 2.5.2 订单管理者
      • 2.5.3 订单控制器
      • 2.5.4 测试报文
      • 2.5.5 测试结果
  • 附录
    • 1、其他相关文章

前言

关于操作日志记录,在一个项目中是必要的。
本文基于 java8 和 SpringBoot 2.7 来实现此功能。

之前写过一个简单的接口报文日志打印的,和本文的起始思路相同,都是使用切面。但是本文功能更为强大,也更复杂。文章见本文附录《SpringBoot自定义starter之接口日志输出》。

本文代码仓库:https://gitee.com/fengsoshuai/custom-log2.git

正文

本文知识点如下:
自定义注解,SpringBoot使用切面,全局异常处理器,ThreadLocal的使用,MDC传递日志ID,登录拦截器,日志拦截器,自定义线程池,SPEL表达式解析,模版方法设计模式等。

一、项目结构介绍

在这里插入图片描述
其中 org.feng.clog 是核心代码区域。org.feng.test 是用于测试功能写的。

二、核心类

在这里插入图片描述

在项目启动时,会把AbstractProcessorTemplate 的子类放入Spring容器。同时会执行注册处理器的方法,其定义如下:

package org.feng.clog;

import lombok.extern.slf4j.Slf4j;
import org.feng.clog.annotation.ProcessorBean;
import org.feng.clog.utils.SpringBeanUtils;

import javax.annotation.PostConstruct;

/**
 * 处理器模板
 *
 * @author feng
 */
@Slf4j
public abstract class AbstractProcessorTemplate<T, R> implements Processor<T, R> {

    protected void init(ProcessorContext<T> context) {
    }

    protected void after(ProcessorContext<T> context, R result) {
    }

    public R start(ProcessorContext<T> context) {
        init(context);

        // 直接调用handle会导致aop失效
        // R result = handle(context);

        AbstractProcessorTemplate<T, R> template = SpringBeanUtils.getByClass(this.getClass());
        R result = template.handle(context);

        after(context, result);

        return result;
    }


    @PostConstruct
    private void registerProcessor() {
        if (this.getClass().isAnnotationPresent(ProcessorBean.class)) {
            ProcessorBean processorBean = this.getClass().getDeclaredAnnotation(ProcessorBean.class);

            log.info("ProcessorBean Register, action is {}, processor is {}", processorBean.action(), this.getClass().getName());
            ProcessorFactory.register(processorBean.action(), this);
        }
    }
}

2.1 核心注解

2.1.1 CLog 日志注解

package org.feng.clog.annotation;

import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.enums.ModuleEnum;

import java.lang.annotation.*;

/**
 * 日志注解</br>
 * <pre>
 * <ul>使用示例:
 * <li>@CLog(template = "这是简单模版,无参数",actionType = ActionTypeEnum.UPDATE,actionIdEl = "{#userReq.id}",moduleEl = "1")</li>
 * <li>@CLog(template = "带参数模版,学生名称:{#userReq.name},班级名称:{#userReq.classReq.name}",actionTypeStr = "这是操作",actionIdEl = "{#userReq.id}")</li>
 * <li>@CLog(template = "带参数计算模版,{#userReq.classReq.number > 20?'大班':'小班'}",actionTypeStr = "这是操作",actionIdEl = "{#userReq.id}")</li>
 * <li>@CLog(template = "复杂模版,{#userReq.classReq.number > 20?'大班':('这是名称:').concat(#userReq.name).concat(',这是年龄:').concat(#userReq.age)}",actionTypeStr = "这是操作",actionIdEl = "{#userReq.id}")</li>
 * <li>@CLog(template = "自定义表达式处理,{SfObjectUtil.isEmpty(#userReq.id)?'id为0或者为空':'id不为0或者为空'}",actionTypeStr = "这是操作",actionIdEl = "{#userReq.id}")</li>
 * <li>@CLog(template = "自定义处理,{logDesc}",actionTypeStr = "这是操作",actionIdEl = "{id}")</li>
 * </ul>
 * </pre>
 *
 * @author feng
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CLog {

    /**
     * 日志模版
     */
    String template();

    /**
     * 模块
     */
    ModuleEnum module() default ModuleEnum.DEFAULT;

    /**
     * 所属模块名
     */
    String moduleStr() default "";

    /**
     * 所属模块名</br>
     * 变量/表达式获取
     */
    String moduleEl() default "";

    /**
     * 操作类型
     */
    ActionTypeEnum actionType() default ActionTypeEnum.DEFAULT;

    /**
     * 操作类型,优先级高于枚举;不为空时强制读取此值
     */
    String actionTypeStr() default "";

    /**
     * 操作类型</br>
     * 变量/表达式获取
     */
    String actionTypeEl() default "";

    /**
     * 业务操作唯一值</br>
     * 变量/表达式获取
     */
    String actionIdEl() default "";

    /**
     * 业务操作唯一值,多值
     */
    String actionIds() default "";

    /**
     * 扩展字段
     */
    String ext() default "";
}

2.1.2 ProcessorBean 处理器bean

package org.feng.clog.annotation;

import org.feng.clog.enums.ActionTypeEnum;

import java.lang.annotation.*;

/**
 * 处理器bean
 *
 * @author feng
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ProcessorBean {

    ActionTypeEnum action();
}

2.2 切面类

package org.feng.clog.aspect;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.feng.clog.LogId;
import org.feng.clog.LogRecordContext;
import org.feng.clog.annotation.CLog;
import org.feng.clog.config.LogCustomerConfig;
import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.enums.ModuleEnum;
import org.feng.clog.utils.SpELParserUtils;
import org.feng.clog.utils.StringUtil;
import org.feng.clog.utils.UserUtil;
import org.feng.clog.vo.UserVo;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 日志切面
 *
 * @author feng
 */
@Aspect
@Component
@Slf4j
public class LogAspect {
    private static final Pattern BRACES_PATTERN = Pattern.compile("\\{.*?}");

    @Resource(name = "logThreadPoolTaskExecutor")
    private Executor  executor;

    @Pointcut("@annotation(org.feng.clog.annotation.CLog)")
    private void pointCut() {
    }

    @AfterReturning(value = "pointCut()")
    public void after(JoinPoint joinPoint) {
        try {
            addLog(joinPoint);
        } finally {
            LogRecordContext.clean();
        }
    }


    public void addLog(JoinPoint joinPoint) {
        String logId = LogId.get();
        UserVo userVo = UserUtil.get();
        Map<String, String> logRecordMap = LogRecordContext.get();

        executor.execute(() -> {
            try {
                // 传递logId到异步线程
                LogId.put(logId);

                // 获取方法+入参
                MethodSignature signature = (MethodSignature) joinPoint.getSignature();
                Object[] args = joinPoint.getArgs();

                // 获取注解
                CLog cLog = signature.getMethod().getDeclaredAnnotation(CLog.class);

                // 获取模版中的参数(如果存在参数),并拼接
                List<String> templateParameters = getTemplateParameters(cLog.template());
                buildTemplateData(templateParameters, signature, args, logRecordMap);
                String template = cLog.template();
                for (String templateParameter : templateParameters) {
                    template = template.replace(templateParameter, logRecordMap.get(templateParameter));
                }

                // 获取module
                String module = getModule(cLog, signature, args, logRecordMap);

                // 获取actionType
                String actionType = getActionType(cLog, signature, args, logRecordMap);

                // 获取actionId
                List<String> actionIds = getActionId(cLog, signature, args, logRecordMap);

                // 获取扩展字段
                JSONObject ext = getExt(cLog, signature, args, logRecordMap);

                if (StringUtil.isNotBlank(template)) {
                    for (String actionId : actionIds) {
                        log.info("记录日志,user={}, template={}, module={}, actionType={}, actionId={}, ext={}", userVo, template, module, actionType, actionId, ext);

                        // todo 日志落库
                    }
                } else {
                    log.info("设置日志数据失败:不满足注解条件");
                }
            } catch (Exception e) {
                log.warn("设置日志异常:", e);
            }
        });
    }


    private List<String> getTemplateParameters(String template) {
        List<String> parameters = new ArrayList<>();
        Matcher matcher = BRACES_PATTERN.matcher(template);
        while (matcher.find()) {
            parameters.add(matcher.group());
        }
        return parameters;
    }


    private void buildTemplateData(List<String> parameters, MethodSignature signature, Object[] args, Map<String, String> map) {
        for (String el : parameters) {
            // 如果EL表达式为空,则直接下一个
            if (!StringUtil.isNotBlank(el)) {
                continue;
            }

            String spEl = el;
            // 兼容自定义数据
            spEl = getEl(spEl);
            if (map.containsKey(spEl)) {
                map.put("{" + spEl + "}", map.get(spEl));
                continue;
            }

            // 自定义类处理
            spEl = parseCustomerMethodEl(spEl);

            // El执行
            if (spEl.contains("#")) {
                String value = SpELParserUtils.parse(signature.getMethod(), args, spEl, String.class);
                map.put(el, value);
            } else {
                map.put(el, "");
            }
        }
    }

    private String getModule(CLog cLog, MethodSignature signature, Object[] args, Map<String, String> map) {
        // 设置了module枚举时,优先获取枚举对应的描述
        if (!ModuleEnum.DEFAULT.equals(cLog.module())) {
            return cLog.module().getDesc();
        }

        // 设置了moduleStr时
        if (StringUtil.isNotBlank(cLog.moduleStr())) {
            return cLog.moduleStr();
        }

        // 设置了moduleEl时
        if (StringUtil.isNotBlank(cLog.moduleEl())) {
            try {
                String el = cLog.moduleEl();
                el = getEl(el);
                // 处理自定义的el
                if (map.containsKey(el)) {
                    return map.get(el);
                }

                // 处理自定义方法el
                el = parseCustomerMethodEl(el);

                // 执行el
                return SpELParserUtils.parse(signature.getMethod(), args, el, String.class);
            } catch (Exception e) {
                log.error("日志切面获取module错误", e);
            }
        }
        return null;
    }


    private String getActionType(CLog cLog, MethodSignature signature, Object[] args, Map<String, String> map) {
        // 设置了actionType枚举时,优先获取枚举对应的描述
        if (!ActionTypeEnum.DEFAULT.equals(cLog.actionType())) {
            return cLog.actionType().getDesc();
        }

        // 设置了actionTypeStr时
        if (StringUtil.isNotBlank(cLog.actionTypeStr())) {
            return cLog.actionTypeStr();
        }

        // 设置了actionTypeEl时
        if (StringUtil.isNotBlank(cLog.actionTypeEl())) {
            String el = cLog.actionTypeEl();
            el = getEl(el);
            // 处理自定义的el
            if (map.containsKey(el)) {
                return map.get(el);
            }

            // 处理自定义方法el
            el = parseCustomerMethodEl(el);

            // 执行el
            return SpELParserUtils.parse(signature.getMethod(), args, el, String.class);
        }
        return null;
    }


    private List<String> getActionId(CLog cLog, MethodSignature signature, Object[] args, Map<String, String> map) {
        // 设置了actionIdEl时
        if (StringUtil.isNotBlank(cLog.actionIdEl())) {
            if (map.containsKey(cLog.actionIdEl())) {
                return Collections.singletonList(map.get(cLog.actionIdEl()));
            }
            String el = cLog.actionIdEl();
            el = getEl(el);
            // 处理自定义el
            if (map.containsKey(el)) {
                return Collections.singletonList(map.get(el));
            }

            // 执行el
            return Collections.singletonList(SpELParserUtils.parse(signature.getMethod(), args, el, String.class));
        }

        // 设置了actionIds时
        if (StringUtil.isNotBlank(cLog.actionIds())) {
            String el = getEl(cLog.actionIds());
            if (map.containsKey(el)) {
                return Arrays.asList(map.get(el).split(","));
            }
        }
        return Collections.singletonList(System.currentTimeMillis() * 10 + new Random().nextInt(10000) + "");
    }

    private JSONObject getExt(CLog cLog, MethodSignature signature, Object[] args, Map<String, String> map) {
        // 如果EL表达式为空,则直接结束
        if (!StringUtil.isNotBlank(cLog.ext())) {
            return null;
        }
        String spEl = cLog.ext();
        //兼容自定义数据
        spEl = getEl(spEl);
        if (map.containsKey(spEl)) {
            String value = map.get(spEl);
            if (StringUtil.isNotBlank(value)) {
                try {
                    return JSONObject.parseObject(value);
                } catch (Exception e) {
                    log.info("JSON转换失败:{},{}", value, e.getMessage());
                    return null;
                }
            }
            return null;
        }

        // 自定义类处理
        spEl = parseCustomerMethodEl(spEl);

        // El执行
        if (spEl.contains("#")) {
            String value = SpELParserUtils.parse(signature.getMethod(), args, spEl, String.class);
            if (StringUtil.isNotBlank(value)) {
                try {
                    return JSONObject.parseObject(value);
                } catch (Exception e) {
                    log.info("JSON转换失败:{},{}", value, e.getMessage());
                    return null;
                }
            }
            return null;
        }
        return null;
    }


    private String parseCustomerMethodEl(String el) {
        for (String key : LogCustomerConfig.getCustomerMethod().keySet()) {
            if (el.contains(key)) {
                String className = key.split("\\.")[0];
                el = el.replace(className, "T(" + LogCustomerConfig.getCustomerMethod().get(key) + ")");
            }
        }
        return el;
    }


    private String getEl(String str) {
        str = str.replaceAll("\\{", "");
        str = str.replaceAll("}", "");
        return str;
    }

}

2.3 自定义线程池

package org.feng.clog.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置
 *
 * @author feng
 */
@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean(name = "logThreadPoolTaskExecutor")
    public Executor initLogCpuExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() + 1);
        executor.setMaxPoolSize(150);
        executor.setQueueCapacity(50);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("log-thread-pool-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        executor.setTaskDecorator(runnable -> runnable);
        return executor;
    }
}

2.4 工具类

2.4.1 管理者工具类

package org.feng.clog.utils;

import org.feng.clog.AbstractProcessorTemplate;
import org.feng.clog.ProcessorContext;
import org.feng.clog.ProcessorFactory;

/**
 * 管理工具
 *
 * @author feng
 */
public class ManagerUtil {
    public static <R, T> R handle(ProcessorContext<T> context) {
        AbstractProcessorTemplate<T, R> processor = ProcessorFactory.getProcessor(context.getAction());
        if (processor == null) {
            throw new RuntimeException("未找到 " + context.getAction() + "对应的处理器");
        }
        return processor.start(context);
    }
}

2.5 测试

2.5.1 订单创建处理器

package org.feng.test;

import lombok.extern.slf4j.Slf4j;
import org.feng.clog.AbstractProcessorTemplate;
import org.feng.clog.LogRecordContext;
import org.feng.clog.ProcessorContext;
import org.feng.clog.annotation.CLog;
import org.feng.clog.annotation.ProcessorBean;
import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.enums.ModuleEnum;
import org.feng.clog.utils.StringUtil;
import org.springframework.stereotype.Service;

/**
 * 创建订单处理器
 *
 * @author feng
 */
@Slf4j
@Service
@ProcessorBean(action = ActionTypeEnum.ORDER_CREATE)
public class OrderCreateProcessor extends AbstractProcessorTemplate<OrderCreateReq, Boolean> {

    @Override
    protected void init(ProcessorContext<OrderCreateReq> context) {
        preHandleReq(context.getData());
    }

    @Override
    @CLog(template = "测试日志记录,{testK1}", module = ModuleEnum.ORDER, actionType = ActionTypeEnum.ORDER_CREATE,
            actionIdEl = "{#context.data.orderNum}", ext = "{JacksonUtil.toJSONString(#context.data)}"
    )
    public Boolean handle(ProcessorContext<OrderCreateReq> context) {

        LogRecordContext.put("testK1", "3wewd2");

        OrderCreateReq orderCreateReq = context.getData();
        log.info("处理--创建订单{}", orderCreateReq.getOrderNum());

        return true;
    }

    @Override
    protected void after(ProcessorContext<OrderCreateReq> context, Boolean result) {
        // todo 后置操作
    }

    private void preHandleReq(OrderCreateReq req) {
        // todo 参数校验

        // 例如校验参数
        if (StringUtil.isBlank(req.getOrderNum())) {
            throw new IllegalArgumentException("订单号不能为空");
        }
    }
}

2.5.2 订单管理者

package org.feng.test;

import org.feng.clog.ProcessorContext;
import org.feng.clog.enums.ActionTypeEnum;
import org.feng.clog.utils.ManagerUtil;
import org.springframework.stereotype.Component;

/**
 * 订单管理
 *
 * @author feng
 */
@Component
public class OrderManager {

    /**
     * 创建订单
     */
    public Boolean createOrder(OrderCreateReq req) {
        ProcessorContext<OrderCreateReq> processorContext = new ProcessorContext<>();
        processorContext.setAction(ActionTypeEnum.ORDER_CREATE);
        processorContext.setData(req);
        return ManagerUtil.handle(processorContext);
    }
}

2.5.3 订单控制器

package org.feng.test;

import org.feng.clog.utils.ResultUtil;
import org.feng.clog.vo.ResultVo;
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;

import javax.annotation.Resource;

/**
 * 控制器
 *
 * @author feng
 */
@RestController
@RequestMapping("order")
public class OrderController {

    @Resource
    private OrderManager orderManager;

    // @WithoutLogin
    @PostMapping("/test1")
    public ResultVo<String> test1(@RequestBody OrderCreateReq req) {
        // 创建
        Boolean started = orderManager.createOrder(req);

        return ResultUtil.success("success " + started);
    }
}

2.5.4 测试报文

{
  "orderNum": "1001",
  "type": 1,
  "senderName": "",
  "likes": ["1", "2", "3"]
}

2.5.5 测试结果

控制台日志输出:

2024-02-28 11:48:40.102  INFO  92309 --- [log-thread-pool-1] org.feng.clog.aspect.LogAspect.lambda$addLog$0(LogAspect.java:95) : [logId=d3b0dc267ce64dfa8a987e8eb6aad4ba] 记录日志,user=UserVo(id=1001, username=feng123, phone=18143431243, email=null), template=测试日志记录,3wewd2, module=订单, actionType=订单创建, actionId=1001, ext={"senderName":"","orderNum":"1001","type":1,"likes":["1","2","3"]}

可以看到,日志中记录了logId,以及日志注解对应的信息。

附录

1、其他相关文章

  • SpringBoot自定义starter之接口日志输出
  • SpringBoot使用线程池之ThreadPoolTaskExecutor和ThreadPoolExecutor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1478017.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker部署前后端服务示例

使用Docker部署js前端 1.创建Dockerfile 在项目跟目录下创建Dockerfile文件&#xff1a; # 使用nginx作为基础镜像 FROM nginx:1.19.1# 指定工作空间 WORKDIR /data/web# 将 yarn build 打包后的build文件夹添加到工作空间 ADD build build# 将项目必要文件添加到工作空间&a…

【Python】FastAPI 项目创建 与 Docker 部署

文章目录 前言&需求描述1. 本地FastAPI1.1 Python 环境准备1.2 本地 Pycharm 创建FastAPI项目 2. Python FastAPI 部署2.1 服务器配置Python环境2.2.1 下载与配置Git、Pyenv等工具2.2.2 下载与配置Python 2.2 FastAPI 打包成镜像2.2.1 项目准备所需环境文件2.2.2 编写Docke…

python matplotlib 三维实体圆柱图

环境 python:python-3.12.0-amd64 包: matplotlib 3.8.2 import matplotlib.pyplot as plt import matplotlib as mpl import numpy as np from mpl_toolkits.mplot3d import Axes3D import matplotlib.ticker as tickerdef map_rate(X: list, to_min: float, to_max: float)…

Restful风格解释

示例对比 传统风格开发 Restful风格开发 结论&#xff1a; 传统风格开发中&#xff0c;前端不同操作使用不同的url来访问后端&#xff0c;使得访问变得麻烦restful风格中&#xff0c;前端使用相同的url来访问后端&#xff0c;但是用数据传送方式进行区分&#xff08;get为请求…

DataGrip2023配置连接Mssqlserver、Mysql、Oracle若干问题解决方案

1、Mssqlserver连接 本人连的是Sql2008&#xff0c;默认添加时&#xff0c;地址、端口、实例、账号、密码后&#xff0c;测试连接出现错误。 Use SSL&#xff1a;不要勾选 VM option&#xff1a;填写&#xff0c;"-Djdk.tls.disabledAlgorithmsSSLv3, RC4, DES, MD5withR…

c#使用log4net的3种调用方法

https://blog.csdn.net/summer_top/article/details/107961245 第一步&#xff1a;下载log4net。 右键项目引用&#xff0c;进入管理NuGet包。 搜索log4net&#xff0c;下载安装。 第二步&#xff1a;创建LogHelper类。 public class LogHelper { private LogHelp…

说一说kong日志级别

Kong官网&#xff1a;The Platform Powering the API World | Kong Inc. Kong Gateway&#xff1a;Kong Gateway | Kong Docs Kong Admin API&#xff1a;Admin API - Kong Gateway - v3.4.x | Kong Docs Kong 企业版社区&#xff1a;API Community for Developers and Industr…

ad18学习笔记十六:如何放置精准焊盘到特定位置,捕抓功能的讲解

网上倒是一堆相关的指导 AD软件熟练度提升&#xff0c;如何设置板框捕捉&#xff1f;_哔哩哔哩_bilibili 关于Altium Designer 20 的捕抓功能的讲解_ad捕捉设置-CSDN博客 AD软件捕捉进阶实例&#xff0c;如何精确的放置布局元器件&#xff1f;_哔哩哔哩_bilibili AD绘制PCB…

android游戏开发大全pdf,赶紧学起来

面经分享 第一部分是我前端面试的经验总结&#xff0c;第二部分是我认为比较有思考空间的题目 经验总结 一份漂亮的简历&#xff0c;需要包括以下部分&#xff08;排版由上而下&#xff09; 个人亮点&#xff08;专精领域&#xff0c;个人博客&#xff0c;开源项目&#xff09…

jmeter 压测数据库

当前版本&#xff1a; jmeter 5.6.3mysql 5.7.39 简介 JMeter 是一个开源的 Java 应用程序&#xff0c;主要用于进行性能测试和负载测试。它支持多种协议&#xff0c;包括但不限于 HTTP、HTTPS、FTP、JDBC 以及各种 Web Services。对于数据库的压力测试可以使用 JDBC 协议与数…

Python算法题集_组合总和

Python算法题集_组合总和 题39&#xff1a;组合总和1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【值传递回溯】2) 改进版一【引用传递堆栈回溯】3) 改进版二【过程值列表缓存遍历后检索】 4. 最优算法5. 相关资源 本文为Python算法题集之一的…

【办公类-22-07】周计划系列(3-2)“信息窗+主题知识(优化)” (2024年调整版本)

作品展示&#xff1a; 背景需求 前文对“2023年2月”的一套信息窗主题知识的文件系列&#xff0c;进行第一次的提取。获得基础模板。 【办公类-22-07】周计划系列&#xff08;3-1&#xff09;“信息窗主题知识&#xff08;提取&#xff09;” &#xff08;2024年调整版本&…

面试官问我Redis怎么测,我哪知道!

有些测试朋友来问我&#xff0c;redis要怎么测试&#xff1f;首先我们需要知道&#xff0c;redis是什么&#xff1f;它能做什么&#xff1f; redis是一个key-value类型的高速存储数据库。 redis常被用做&#xff1a;缓存、队列、发布订阅等。 所以&#xff0c;“redis要怎么测试…

理解计算着色器中glsl语言的内置变量

概要 本文通过示例的方式&#xff0c;着重解释以下几个内置变量&#xff1a; gl_WorkGroupSizegl_NumWorkGroupsgl_LocalInvocationIDgl_WorkGroupIDgl_GlobalInvocationID 基本概念 局部工作组与工作项 一个3x2x1的局部工作组示例如下&#xff0c;每个小篮格子表示一个工作项…

简单了解B树和B+树

目录 B树 B树 B树和B树的结构示意图 总结 B树和B树是两种非常重要的树状数据结构&#xff0c;它们广泛应用于数据库和文件系统的索引结构中。这两种数据结构能够帮助我们高效地管理、查询以及更新大量的数据。下面&#xff0c;我将简单介绍它们,以及他们之间的区别。 B树 B…

【leetcode】链表的中间节点

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家刷题&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 点击查看题目 思路: slow和fast都初始化为head&#xff0c;之后slow每走1步&#xff0c;fast走2步…

枚举(蓝桥练习)(反倍数、特别数的和、找到最多的数、小蓝的漆房、小蓝和小桥的挑战)

目录 一、枚举算法介绍 二、解空间的类型 三、循环枚举解空间 四、例题 &#xff08;一、反倍数&#xff09; &#xff08;二、特别数的和&#xff09; &#xff08;三、找到最多的数&#xff09; &#xff08;四、小蓝的漆房&#xff09; &#xff08;五、小蓝和小桥的…

【力扣hot100】刷题笔记Day17

前言 今天竟然不用开组会&#xff01;天大的好消息&#xff0c;安心刷题了 46. 全排列 - 力扣&#xff08;LeetCode&#xff09; 回溯&#xff08;排列&#xff09; class Solution:def permute(self, nums: List[int]) -> List[List[int]]:# 回溯def backtrack():if len(…

一、前端开发

#视频链接&#xff1a;https://www.bilibili.com/video/BV1rT4y1v7uQ?p1&vd_source1717654b9cbbc6a773c2092070686a95 前端开发 前端开发1、快速开发网站2、浏览器能识别的标签2.1 编码&#xff08;head&#xff09;2.2 title(head)2.3 标题2.4 div和span练习题2.5 超链接…

如何做代币分析:以 USDT 币为例

作者&#xff1a;lesleyfootprint.network 编译&#xff1a;cicifootprint.network 数据源&#xff1a;USDT Token Dashboard &#xff08;仅包括以太坊数据&#xff09; 在加密货币和数字资产领域&#xff0c;代币分析起着至关重要的作用。代币分析指的是深入研究与代币相关…