基于 mzt-biz-log 实现接口调用日志记录

news2024/12/20 14:37:05

🎯导读:mzt-biz-log 是一个用于记录操作日志的通用组件,旨在追踪系统中“谁”在“何时”对“何事”执行了“何种操作”。该组件通过简单的注解配置,如 @LogRecord,即可实现接口调用的日志记录,支持成功与失败场景下的差异化日志描述。它还提供了丰富的功能,包括但不限于租户隔离、日志子类型划分、条件性日志记录以及枚举值解析等。此外,mzt-biz-log 支持自定义日志存储逻辑,允许开发者根据业务需求将日志持久化到数据库或其他存储媒介。整体设计简洁高效,适用于微服务架构中的日志管理需求。

文章目录

  • mzt-biz-log介绍
  • 具体实现
    • 依赖
    • 添加注解
    • 枚举类型转化为具体值
      • 枚举类
      • 实现解析器类
      • 使用
    • 日志子类型划分
    • 日志过滤
    • 日志持久化
      • 数据库
      • 继承存储接口

mzt-biz-log介绍

mzt-biz-log:一套通用操作日志组件,用来记录「谁」在「什么时间」对「什么」做了「什么事」

  • github仓库:https://github.com/mouzt/mzt-biz-log

具体实现

依赖

<dependency>
    <groupId>io.github.mouzt</groupId>
    <artifactId>bizlog-sdk</artifactId>
    <version>3.0.6</version>
</dependency>

添加注解

首先需要在具体的服务启动类中添加注解@EnableLogRecord(tenant = "venue"),其中tenant是租户标识,我这里设置为了服务的名称,一般一个服务或者一个业务下的多个服务都用一个 tenant 就可以了

在这里插入图片描述

然后在具体的接口添加注解@LogRecord,在调用相应的接口之后,就会触发日志

@Repeatable(LogRecords.class)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LogRecord {
    String success();

    String fail() default "";

    String operator() default "";

    String type();

    String subType() default "";

    String bizNo();

    String extra() default "";

    String condition() default "";

    String successCondition() default "";
}
  • type:日志类型,可以用来区分不同的接口,我这里直接设置为接口名称,方便辨识
  • subType:日志子类型,可以用来区分不同的操作者身份
  • bizNo:日志ID,可以设置为具体的数据的ID,这样查询日志的时候,直接使用相应数据的ID来查询,例如说bizNo存储的是订单ID,后面可以凭借这个来查询该订单相关的日志
  • success:接口调用成功之后,action存放什么数据(action字段是什么,看日志持久化就知道了),一般通过描述语言拼接字段值来实现快速让用户知道日志的内容
  • fail:接口调用异常之后,action存放什么数据
  • extra:需要记录的额外信息,如直接将用户提交的数据的 json 进行存储,因为action存储的是简略的信息
  • operator:存储操作人信息,需要用户的系统已经实现了用户上下文

【成功调用示例】

/**
 * 增添数据
 */
@PostMapping("/save")
@LogRecord(
        bizNo = "{{#id}}",
        type = "新增分区",
        success = """
            场馆ID{{#partitionDO.venueId}}, \
            分区名称:{{#partitionDO.name}}, \
            分区类型:{{#partitionDO.type}}, \
            描述:{{#partitionDO.description}}, \
            场区拥有的场数量:{{#partitionDO.num}}, \
            场区状态:{{#partitionDO.status}}; \
            结果:{{#_ret}}
            """,
        fail = "接口调用失败,失败原因:{{#_errorMsg}}",
        extra = "{{#partitionDO.toString()}}",
        operator = "{{T(com.vrs.common.context.UserContext).getUsername()}}"
)
public Result save(@Validated({AddGroup.class}) @RequestBody PartitionDO partitionDO) {
    partitionService.save(partitionDO);
    // 因为 ID 是存储到数据库中才生成的,@LogRecord 默认拿不到,需要我们将信息手动设置到上下文中
    LogRecordContext.putVariable("id", partitionDO.getId());
    return Results.success();
}

注意:

  • 获取接口返回的结果:{{#_ret}}
  • 通过日志上下文记录信息:因为 id 是存储到数据库中才生成的,@LogRecord 一开始拿不到,需要我们将信息手动设置到上下文中。可以通过LogRecordContext.putVariable("id", partitionDO.getId());来设置键值对,然后在注解中凭借键来获取值就可以,如bizNo = "{{#id}}"

在这里插入图片描述

接口调用成功的日志内容如下:

【logRecord】log=LogRecord(id=null, tenant=venue, type=新增分区, subType=, bizNo=1868205198032568320, operator=admin, action=场馆ID:12345, 分区名称:篮球场A区, 分区类型:营业中, 描述:提供标准篮球设施,包括篮球和球架。, 场区拥有的场数量:4, 场区状态:篮球; 结果:Result(code=0, message=null, data=null, requestId=null)
, fail=false, createTime=Sun Dec 15 16:03:23 CST 2024, extra=PartitionDO(venueId=1865271207637635072, name=篮球场A区, type=1, description=提供标准篮球设施,包括篮球和球架。, num=4, status=1), codeVariable={MethodName=save, ClassName=class com.vrs.controller.PartitionController})

【失败调用示例】

首先在接口中模拟一个除以 0 异常,即System.out.println(1/0);。然后在注解中添加fail = "接口调用失败,失败原因:{{#_errorMsg}}",其中#_errorMsg获取的是异常的信息

@PostMapping("/save")
@LogRecord(
        bizNo = "{{#id}}",
        type = "新增分区",
        success = """
            场馆ID{{#partitionDO.venueId}}, \
            分区名称:{{#partitionDO.name}}, \
            分区类型:{{#partitionDO.type}}, \
            描述:{{#partitionDO.description}}, \
            场区拥有的场数量:{{#partitionDO.num}}, \
            场区状态:{{#partitionDO.status}}; \
            结果:{{#_ret}}
            """,
        fail = "接口调用失败,失败原因:{{#_errorMsg}}",
        extra = "{{#partitionDO.toString()}}",
        operator = "{{T(com.vrs.common.context.UserContext).getUsername()}}"
)
public Result save(@Validated({AddGroup.class}) @RequestBody PartitionDO partitionDO) {
    partitionService.save(partitionDO);
    // 因为 ID 是存储到数据库中才生成的,@LogRecord 默认拿不到,需要我们将信息手动设置到上下文中
    LogRecordContext.putVariable("id", partitionDO.getId());
    System.out.println(1/0);
    return Results.success();
}

调用失败之后的日志如下:

【logRecord】log=LogRecord(id=null, tenant=venue, type=新增分区, subType=, bizNo=1868210725902950400, operator=admin, action=接口调用失败,失败原因:/ by zero, fail=true, createTime=Sun Dec 15 16:25:21 CST 2024, extra=PartitionDO(venueId=1865271207637635072, name=篮球场A, type=1, description=提供标准篮球设施,包括篮球和球架。, num=4, status=1), codeVariable={MethodName=save, ClassName=class com.vrs.controller.PartitionController})

枚举类型转化为具体值

上面日志输出中,分区类型和场区状态的是具体的数字值

在这里插入图片描述

如果说想要将类型对应为具体的值,应该如何实现呢?

枚举类

【场馆类型枚举】

package com.vrs.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

/**
 * 场馆类型枚举
 */
@RequiredArgsConstructor
public enum PartitionStatusEnum {

    BASKET_BALL(1, "篮球"),
    FOOT_BALL(2, "足球"),
    BADMINTON(3, "羽毛球"),
    VOLLEYBALL(4, "排球"),
    TABLE_TENNIS(5, "乒乓球"),
    TENNIS(6, "网球"),
    SWIMMING(7, "游泳"),
    GYMNASTICS(8, "体操"),
    FITNESS_CENTER(9, "健身房"),
    HANDBALL(10, "手球"),
    ICE_SKATING(11, "滑冰"),
    SKATEBOARDING(12, "滑板"),
    CLIMBING(13, "攀岩"),
    CYCLING_INDOOR(14, "室内自行车"),
    YOGA(15, "瑜伽");

    @Getter
    private final int type;

    @Getter
    private final String value;

    /**
     * 根据 type 找到对应的 value
     *
     * @param type 要查找的类型代码
     * @return 对应的描述值,如果没有找到抛异常
     */
    public static String findValueByType(int type) {
        for (PartitionStatusEnum target : PartitionStatusEnum.values()) {
            if (target.getType() == type) {
                return target.getValue();
            }
        }
        throw new IllegalArgumentException();
    }
}

【场区状态枚举】

package com.vrs.enums;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

/**
 * 场区状态枚举
 */
@RequiredArgsConstructor
public enum VenueTypeEnum {

    CLOSED(0, "已关闭"),
    OPEN(1, "营业中"),
    MAINTAIN(2, "维护中");

    @Getter
    private final int type;

    @Getter
    private final String value;

    /**
     * 根据 type 找到对应的 value
     *
     * @param type 要查找的类型代码
     * @return 对应的描述值,如果没有找到抛异常
     */
    public static String findValueByType(int type) {
        for (VenueTypeEnum target : VenueTypeEnum.values()) {
            if (target.getType() == type) {
                return target.getValue();
            }
        }
        throw new IllegalArgumentException();
    }
}

实现解析器类

转换类需要继承IParseFunction接口,然后实现两个方法

  • functionName:返回解析器的标识,后面需要在注解中使用来辨识不同的解析器
  • apply:主要用来实现解析工作,如将枚举类型转化为具体的值
package com.vrs.biglog;

import com.mzt.logapi.service.IParseFunction;
import com.vrs.enums.PartitionStatusEnum;
import org.springframework.stereotype.Component;

/**
 * @Author dam
 * @create 2024/12/15 16:43
 */
@Component
public class PartitionStatusEnumParse implements IParseFunction {

    @Override
    public String functionName() {
        return "PartitionStatusEnumParse";
    }

    @Override
    public String apply(Object value) {
        return PartitionStatusEnum.findValueByType(Integer.parseInt(value.toString()));
    }
}
package com.vrs.biglog;

import com.mzt.logapi.service.IParseFunction;
import com.vrs.enums.VenueTypeEnum;
import org.springframework.stereotype.Component;

/**
 * @Author dam
 * @create 2024/12/15 16:43
 */
@Component
public class VenueTypeEnumParse implements IParseFunction {

    @Override
    public String functionName() {
        return "VenueTypeEnumParse";
    }

    @Override
    public String apply(Object value) {
        return VenueTypeEnum.findValueByType(Integer.parseInt(value.toString()));
    }
}

使用

@PostMapping("/save")
@LogRecord(
        bizNo = "{{#id}}",
        type = "新增分区",
        success = """
            场馆ID{{#partitionDO.venueId}}, \
            分区名称:{{#partitionDO.name}}, \
            分区类型:{VenueTypeEnumParse{#partitionDO.type}}, \
            描述:{{#partitionDO.description}}, \
            场区拥有的场数量:{{#partitionDO.num}}, \
            场区状态:{PartitionStatusEnumParse{#partitionDO.type}};\
            结果:{{#_ret}}
            """,
        fail = "接口调用失败,失败原因:{{#_errorMsg}}",
        extra = "{{#partitionDO.toString()}}",
        operator = "{{T(com.vrs.common.context.UserContext).getUsername()}}"
)
public Result save(@Validated({AddGroup.class}) @RequestBody PartitionDO partitionDO) {
    partitionService.save(partitionDO);
    // 因为 ID 是存储到数据库中才生成的,@LogRecord 默认拿不到,需要我们将信息手动设置到上下文中
    LogRecordContext.putVariable("id", partitionDO.getId());
    return Results.success();
}

注意分区类型:{VenueTypeEnumParse{#partitionDO.type}}中使用了解析器的标识

重新运行之后,发现枚举类型已经转化了具体值

在这里插入图片描述

日志子类型划分

日志子类型划分为了区分日志的所属身份,比如说普通用户修改了数据,管理员也修改了数据。但通常指允许管理员查看用户的操作日志,不允许普通用户查看管理员的操作日志。因此可以使用subType字段来做一些区分,后面实现日志查询的时候,针对用户的身份对该字段做一些处理即可

@LogRecord(
        bizNo = "{{#id}}",
        type = "新增分区",
        subType = "{{T(com.vrs.common.context.UserContext).getUserType()}}",
        success = """
                场馆ID{{#partitionDO.venueId}}, \
                分区名称:{{#partitionDO.name}}, \
                分区类型:{VenueTypeEnumParse{#partitionDO.type}}, \
                描述:{{#partitionDO.description}}, \
                场区拥有的场数量:{{#partitionDO.num}}, \
                场区状态:{PartitionStatusEnumParse{#partitionDO.type}}; \
                结果:{{#_ret}}
                """,
        fail = "接口调用失败,失败原因:{{#_errorMsg}}",
        extra = "{{#partitionDO.toString()}}",
        operator = "{{T(com.vrs.common.context.UserContext).getUsername()}}"
)

日志过滤

只有在满足一定条件的时候,才记录日志,可以使用condition字段,比如说用户提交的数量为null,才记录日志

@LogRecord(
        bizNo = "{{#id}}",
        type = "新增分区",
        subType = "{{T(com.vrs.common.context.UserContext).getUserType()}}",
        success = """
                场馆ID{{#partitionDO.venueId}}, \
                分区名称:{{#partitionDO.name}}, \
                分区类型:{VenueTypeEnumParse{#partitionDO.type}}, \
                描述:{{#partitionDO.description}}, \
                场区拥有的场数量:{{#partitionDO.num}}, \
                场区状态:{PartitionStatusEnumParse{#partitionDO.type}}; \
                结果:{{#_ret}}
                """,
        fail = "接口调用失败,失败原因:{{#_errorMsg}}",
        extra = "{{#partitionDO.toString()}}",
        operator = "{{T(com.vrs.common.context.UserContext).getUsername()}}",
        condition = "{{#partitionDO.num == null}}"
)

日志持久化

数据库

DROP TABLE IF EXISTS `mt_biz_log`;
CREATE TABLE `mt_biz_log` (
  `id` bigint NOT NULL COMMENT 'ID',
  `create_time` datetime,
  `update_time` datetime,
  `is_deleted` tinyint default 0 COMMENT '逻辑删除 0:没删除 1:已删除',
  `tenant` varchar(50) DEFAULT NULL COMMENT '租户',
  `type` varchar(50) DEFAULT NULL COMMENT '类型',
  `sub_type` varchar(50) DEFAULT NULL COMMENT '子类型',
  `class_name` varchar(100) DEFAULT NULL COMMENT '方法名称',
  `method_name` varchar(100) DEFAULT NULL COMMENT '方法名称',
  `operator` varchar(50) DEFAULT NULL COMMENT '操作人员',
  `action` longtext COMMENT '操作',
  `extra` longtext COMMENT '其他补充',
  `status` tinyint DEFAULT NULL COMMENT '操作状态 (0正常 1异常)',
  PRIMARY KEY (`id`) USING BTREE
) COMMENT='操作日志表';

【实体类】

package com.vrs.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.vrs.domain.base.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * 操作日志表
 * @TableName mt_biz_log
 */
@TableName(value ="mt_biz_log")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MtBizLog extends BaseEntity implements Serializable {

    /**
     * 租户
     */
    private String tenant;

    /**
     * 类型
     */
    private String type;

    /**
     * 子类型
     */
    private String subType;

    /**
     * 方法名称
     */
    private String className;

    /**
     * 方法名称
     */
    private String methodName;

    /**
     * 操作人员
     */
    private String operator;

    /**
     * 操作
     */
    private String action;

    /**
     * 其他补充
     */
    private String extra;

    /**
     * 操作状态 (0正常 1异常)
     */
    private Integer status;

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

增删改查方法我这里就不再介绍了,请大家自行实现

继承存储接口

只需要实现ILogRecordService接口,然后重写record方法,然后在该方法里面调用持久化方法即可

我这里统一将所有日志记录到一个表中,如果想要根据业务分表存储,可以根据logRecord.getTenant()logRecord.getType()来判断存储到哪个表即可

package com.vrs.service.impl;

import com.mzt.logapi.beans.CodeVariableType;
import com.mzt.logapi.beans.LogRecord;
import com.mzt.logapi.service.ILogRecordService;
import com.vrs.entity.MtBizLog;
import com.vrs.service.MtBizLogService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Author dam
 * @create 2024/12/15 21:02
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class BizlogStoreService implements ILogRecordService {

    private final MtBizLogService mtBizLogService;

    @Override
    public void record(LogRecord logRecord) {
        mtBizLogService.save(MtBizLog.builder()
                .tenant(logRecord.getTenant())
                .type(logRecord.getType())
                .subType(logRecord.getSubType())
                .className(logRecord.getCodeVariable().get(CodeVariableType.ClassName).toString())
                .methodName(logRecord.getCodeVariable().get(CodeVariableType.MethodName).toString())
                .operator(logRecord.getOperator())
                .action(logRecord.getAction())
                .extra(logRecord.getExtra())
                .status(logRecord.isFail() ? 1 : 0)
                .build());
    }

    @Override
    public List<LogRecord> queryLog(String bizNo, String type) {
        return null;
    }

    @Override
    public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {
        return null;
    }
}

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

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

相关文章

芯片级IO (Pad) Ring IP Checklist

SoC top顶层数字后端实现都会涉及到IO Ring &#xff08;PAD Ring&#xff09;的设计。这里面包括VDD IO,VDDIO IO, Signal IO, Corner IO&#xff0c;Filler IO&#xff0c;IO power cut cell等等。 数字后端零基础入门系列 | Innovus零基础LAB学习Day2 数字IC后端实现TOP F…

OpenHarmony-3.HDF Display子系统(6)

Display 子系统 1.Display驱动模型介绍 当前操作系统和 SOC 种类繁多&#xff0c;各厂商的显示屏器件也各有不同&#xff0c;随之针对器件的驱动代码也不尽相同&#xff0c;往往是某一款器件驱动&#xff0c;只适用于某单一内核系统或 SOC&#xff0c;如果要迁移到其他内核或者…

一个日期范围选择框的设计

一个日期范围选择框的设计 一个日期范围选择框 这是一个日期范围选择框的设计&#xff0c;除了可以选择开始日期、结束日期之外。还要一些按钮用于快速选择日期范围&#xff1a; Today今天Yesterday昨天Today & Yesterday今天和昨天This month当月Last month上个月Last w…

【中标麒麟服务器操作系统实例分享】java应用DNS解析异常分析及处理

了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer.kylinos.cn 文档中心&#xff1a;https://documentkylinos.cn 情况描述 中标麒麟服务器操作系统V7运行在 ARM虚…

React 第十七节 useMemo用法详解

概述 useMemo 是React 中的一个HOOK&#xff0c;用于根据依赖在每次渲染时候缓存计算结果&#xff1b; 大白话就是&#xff0c;只有依赖项发生变化时候&#xff0c;才会重新渲染为新计算的值&#xff0c;否则就还是取原来的值&#xff0c;有点类似 vue 中的 computed 计算属性…

景联文科技:精准语音标注,驱动语音技术新发展

在人工智能迅速发展的今天&#xff0c;语音技术的应用已经渗透到我们生活的方方面面。从智能音箱、语音助手到自动语音识别系统&#xff0c;高质量的语音数据是这些应用成功的关键。景联文科技作为领先的AI数据服务提供商&#xff0c;专注于为客户提供高精度、高效的语音标注服…

中文分词学习

1.安装 jieba 库 !pip install jieba jieba 库是用于中文分词的工具&#xff0c;它通过精确的分词算法来处理文本。通过分词可以将中文句子拆分成单独的词语&#xff0c;这对于自然语言处理任务非常重要&#xff0c;比如文本分类、情感分析、关键词提取。 2.中文文本分词处理…

Android 动画深度解析

一、Android 动画发展历程与核心类型总览 自 Android 诞生起&#xff0c;动画系统便不断推陈出新。早期存在补间动画&#xff08;Tween Animation&#xff09;与帧动画&#xff08;Frame Animation&#xff09;&#xff0c;而 Android 3.0 重磅引入属性动画&#xff08;Proper…

Linux-ubuntu之主频和时钟配置

Linux-ubuntu之主频和时钟配置 一&#xff0c;主频二&#xff0c;其它时钟配置1.PLL2和PLL3的PFD0-3设置2.AHB_CLK_ROOT3.IPG 和 PERCLK时钟 三&#xff0c;总结 一&#xff0c;主频 24MHz 晶振为内核和其它外设提供时钟源&#xff0c;经电路后到PLL1变为996MHZ&#xff0c;再…

Autosar入门_汽车电子控制器

上一篇 | 返回主目录 汽车电子控制器 1 汽车电子控制器定义2 从功能角度来看构成2.1 车门控制器简单示例2.1.1 解锁过程分析2.1.2 无框车窗短降2.1.3 下电控制电耗2.2 控制器几大基本功能 3 从硬件构成角度看构成3.1 芯片类别说明3.2 芯片与功能联系 1 汽车电子控制器定义 汽…

变压器“变压”和“变流”说明

变压器可以改变交流电压的大小&#xff0c;也可以改变交流电流的大小。 改变交流电压 变压器既可以升高交流电压&#xff0c;也能降低交流电压。在忽略电能损耗的情况下&#xff0c;变 压器电压U、二次电压U2与烧组匝数N、二次烧组匝数的关系为&#xff1a; n称为匝数比或电…

初学stm32 --- 时钟配置

目录 stm32时钟系统 时钟源 &#xff08;1&#xff09; 2 个外部时钟源&#xff1a; &#xff08;2&#xff09;2 个内部时钟源&#xff1a; 锁相环 PLL PLLXTPRE&#xff1a; HSE 分频器作为 PLL 输入 (HSE divider for PLL entry) PLLSRC&#xff1a; PLL 输入时钟源 (PL…

[机器学习]XGBoost(3)——确定树的结构

XGBoost的目标函数详见[机器学习]XGBoost&#xff08;2&#xff09;——目标函数&#xff08;公式详解&#xff09; 确定树的结构 之前在关于目标函数的计算中&#xff0c;均假设树的结构是确定的&#xff0c;但实际上&#xff0c;当划分条件不同时&#xff0c;叶子节点包含的…

常用命名总结

命名在编程中是非常重要的&#xff0c;它直接影响到代码的可读性、可维护性和开发效率。一个好的命名能够让代码更加直观、易于理解和修改&#xff0c;反之&#xff0c;不恰当的命名可能导致混乱、错误和难以调试的问题。以下是一些关于命名的最佳实践和原则&#xff1a; 1. 简…

AutoMQ 流表一体新特性 Table Topic 发布: 无缝集成 AWS S3 Table 和 Iceberg

超越共享存储&#xff1a;使用 Apache Iceberg 中的 AutoMQ Table Topic 实现流处理与分析的统一 自 2023 年底官宣以来&#xff0c;AutoMQ 成功地将 Apache Kafka 从“Shared Nothing architecture”转变为“Shared Storage architecture”&#xff0c;这为京东、知乎、小红书…

maven使用Dependency-Check来扫描安全漏洞

在现代软件开发中&#xff0c;使用开源库和第三方依赖项已成为常态。然而&#xff0c;这些依赖项可能包含已知的安全漏洞&#xff0c;给应用程序带来潜在的风险。为了解决这个问题&#xff0c;OWASP Dependency-Check 应运而生。本文将介绍 OWASP Dependency-Check 的功能、安装…

#渗透测试#红队全栈 powshell基础使用

声明&#xff01; 学习视频来自B站up主 泷羽sec&#xff0c;任何违法事件与本人以及泷羽sec团队无关&#xff0c;切勿触碰法律底线&#xff0c;否则后果自负&#xff01;&#xff01;&#xff01;&#xff01; 目录标题 认识powsehll打开方式 使用方式美化自己的powershell简单…

Qt:QMetaObject::connectSlotsByName实现信号槽自动关联

简介 在Qt中&#xff0c;QMetaObject::connectSlotsByName 是一个便利的方法&#xff0c;它可以根据对象的对象名&#xff08;objectName&#xff09;自动将信号和槽连接起来。但是&#xff0c;要使用这个方法&#xff0c;必须确保&#xff1a; 1 控件&#xff08;如按钮&…

《算法ZUC》题目

判断题 ZUC算法LFSR部分产生的二元序列具有很低的线性复杂度。 A.正确 B.错误 正确答案A 单项选择题 ZUC算法驱动部分LFSR的抽头位置不包括&#xff08; &#xff09;。 A.s15 B.s10 C.s7 D.s0 正确答案C 单项选择题 ZUC算法比特重组BR层主要使用了软件实现友好的…

maven项目中对不同目录下的同包同名类的引用情况整理

说明 maven项目&#xff0c;允许在不同目录中出现相同包名和相同类名&#xff0c;不会出现冲突&#xff0c;包括&#xff1a; java目录test目录依赖中目录 这里就用Hutool.class类中的一个常量做测试&#xff0c;如图 好奇同包同名类同时存在时&#xff0c;会加载哪个类 Syst…