当前位置: 首页 > news >正文

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

🎯导读: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, "瑜伽");@Getterprivate final int type;@Getterprivate 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, "维护中");@Getterprivate final int type;@Getterprivate 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 {@Overridepublic String functionName() {return "PartitionStatusEnumParse";}@Overridepublic 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 {@Overridepublic String functionName() {return "VenueTypeEnumParse";}@Overridepublic 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;@Overridepublic 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());}@Overridepublic List<LogRecord> queryLog(String bizNo, String type) {return null;}@Overridepublic List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {return null;}
}

相关文章:

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

&#x1f3af;导读&#xff1a;mzt-biz-log 是一个用于记录操作日志的通用组件&#xff0c;旨在追踪系统中“谁”在“何时”对“何事”执行了“何种操作”。该组件通过简单的注解配置&#xff0c;如 LogRecord&#xff0c;即可实现接口调用的日志记录&#xff0c;支持成功与失败…...

docker容器的安装以及用法

1、了解docker 1.1、docker是什么 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现…...

Java中的Consumer接口应该如何使用(通俗易懂图解)

应用场景&#xff1a; 第一次程序员A写好了个基础的遍历方法&#xff1a; public class Demo1 {public static void main(String[] args) {//假设main方法为程序员B写的,此时需要去调用A写好的一个遍历方法//1.如果此时B突然发现想将字符串以小写的形式打印出来&#xff0c;则…...

D102【python 接口自动化学习】- pytest进阶之fixture用法

day102 pytest的usefixtures方法 学习日期&#xff1a;20241219 学习目标&#xff1a;pytest基础用法 -- pytest的usefixtures方法 学习笔记&#xff1a; fixture调用方法 实际应用 总结 pytest.mark.usefixtures(func)&#xff0c;pytest的usefixtures方法&#xff0c;无…...

从零玩转CanMV-K230(4)-小核Linux驱动开发参考

前言 K230 芯片是一款基于 RISC-V 架构的端侧 AIoT 芯片&#xff0c;包含两个核心&#xff1a; CPU 1: RISC-V 处理器&#xff0c;1.6GHz&#xff0c;32KB I-cache, 32KB D-cache, 256KB L2 Cache&#xff0c;128bit RVV 1.0扩展 CPU 0: RISC-V 处理器&#xff0c;0.8GHz&am…...

上汽大通汽车CAN数据解析开发服务及技术商用领域详细解析

上汽大通G90是一款集豪华、科技与舒适于一身的中大型MPV&#xff0c;号称“国产埃尔法”。在国内市场&#xff0c;作为“卷王”的G90主要面向中大型MPV市场&#xff0c;满足家庭出行、商务接待和客运租赁等多元化场景需求&#xff0c;在国内市场上取得了不错的销售成绩。在海外…...

基于SCUI的后台管理系统

一、SCUI Admin 官方地址&#xff1a;https://python-abc.xyz/scui-doc/ 高性能中后台前端解决方案&#xff0c;基于 Vue3、elementPlus 持续性的提供独家组件和丰富的业务模板帮助你快速搭建企业级中后台前端任务。 预览地址&#xff1a;https://python-abc.xyz/scui-doc/de…...

使用频谱分析仪:RBW,Res BW,分辨率带宽;Sweep,扫描;noise floor,底噪,如何降低底噪?

RBW与Sweep的定义及其特性阐述&#xff1a; Res BW&#xff0c;即Resolution Bandwidth&#xff08;分辨率带宽&#xff09;&#xff0c;是衡量仪器分辨信号细节能力的重要参数。当RBW的数值越小&#xff0c;意味着像素点的尺寸更为精细&#xff0c;从而能够观察到更为细微的信…...

项目管理工具Maven(一)

Maven的概念 什么是Maven 翻译为“专家”&#xff0c;“内行”Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建&#xff0c;依赖管理和项目信息管理。什么是理想的项目构建&#xff1f; 高度自动化&#xff0c;跨平台&#xff0c;可重用的组件&#xff0c;标准…...

阿里云ESC服务器一次性全部迁移到另一个ESC

摘要&#xff1a; 在云计算时代&#xff0c;服务器迁移是企业优化资源配置、提升业务灵活性的常见需求。本文将详细介绍如何将阿里云ECS&#xff08;Elastic Compute Service&#xff09;服务器一次性迁移到另一个ECS实例。整个迁移过程分为四个关键步骤&#xff1a; 创建自定义…...

搭建分布式Kafka集群

title: 搭建分布式Kafka集群 date: 2024-12-1 14:00:00 categories: - 服务器 tags: - Kafka - 大数据搭建分布式Kafka集群 在主节点上安装Kafka&#xff1b; Kafka使用Zookeeper服务器来存储元数据信息 本次实验环境&#xff1a;Centos 7-2009、Hadoop-3.1.4、JDK 8、Zookeep…...

【后端面试总结】深入解析进程和线程的区别

在操作系统和并发编程中&#xff0c;进程和线程是两个核心概念。它们各自承担着不同的职责&#xff0c;并在多任务处理中发挥着关键作用。本文将从定义、特性、应用场景以及优缺点等多个方面对进程和线程进行详细对比&#xff0c;帮助读者深入理解它们之间的区别。 一、进程和…...

java版电子招投标采购|投标|评标|竞标|邀标|评审招投标系统源码

招投标管理系统是一款适用于招标代理、政府采购、企业采购和工程交易等领域的企业级应用平台。该平台以项目为主线&#xff0c;从项目立项到项目归档&#xff0c;实现了全流程的高效沟通和协作。通过该平台&#xff0c;用户可以实时共享项目数据信息&#xff0c;实现规范化管理…...

SSM 赋能 Vue 助力:新锐台球厅管理系统的设计与实现的辉煌之路

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常适…...

C++ 并发专题 - C++线程同步的几种方法

一&#xff1a;概述 线程同步是多线程编程中的一个重要概念&#xff0c;它用于控制多个线程之间对共享资源的访问&#xff0c;避免竞态条件&#xff08;race condition&#xff09;和数据不一致的问题。线程同步确保在多线程环境中&#xff0c;多个线程访问共享数据时能够按照某…...

使用Python脚本进行编写批量根据源IP进行查询的语句用于态势感知攻击行为的搜索

使用Python脚本进行编写批量根据源IP进行查询的语句 以下根据ip-list集里面的IP地址&#xff08;可以自行扩充&#xff09;&#xff0c;然后采用srcaddress "{ip}" or 的形式进行打印并存储在路径为&#xff1a;桌面的IOC结果.txt --------------------------代码如…...

Python中的zip/unzip:像拉拉链一样组合数据的艺术

今天让我们一起探讨Python中一个优雅而强大的内置功能&#xff1a; zip 和 unzip 。听名字就知道&#xff0c;它就像我们衣服上的拉链一样&#xff0c;能把两边的数据完美地咬合在一起。 从一个有趣的例子开始 想象你正在开发一个班级管理系统。每个学生都有名字、成绩和评语…...

数电课设·简易数字钟(Quartus Ⅱ)

忽如一夜春风来&#xff0c;千树万树梨花开 —— 《白雪歌诵武判官归京》 岑参 【唐】 目录 简易数字钟 要点剖析&#xff1a; 逐步分析&#xff1a; 端口说明&#xff1a; 代码展示&#xff1a; 分部解释&#xff1a; 代码编译结果&#xff1a; 提醒 &#xff1a; …...

大模型中RAG模型的检索过程是如何实现的?(附最佳实践资料)

RAG模型的检索过程主要涉及以下几个步骤&#xff1a; 向量化&#xff08;Embedding&#xff09;&#xff1a;首先&#xff0c;需要将外部知识库中的文档转换为计算机能够理解的向量形式。这一步骤通常使用预训练的嵌入模型&#xff08;如BERT、GPT等&#xff09;将文本转换为高…...

python:用 sklearn.metrics 评价 K-Means 聚类模型

sklearn 的 metrics 模块提供的聚类模型评价指标如下&#xff1a; ARI 评价法&#xff08;兰德系数&#xff09;: adjusted_rand_score AMI 评价法&#xff08;相互信息&#xff09;: adjusted_mutual_info_score V-measure 评分 : completeness_score FMI 评价法 : fowlkes_m…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型&#xff08;Vision-Language Models, VLMs&#xff09;&#xff0c;为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展&#xff0c;机器人仍难以胜任复杂的长时程任务&#xff08;如家具装配&#xff09;&#xff0c;主要受限于人…...

GO协程(Goroutine)问题总结

在使用Go语言来编写代码时&#xff0c;遇到的一些问题总结一下 [参考文档]&#xff1a;https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现&#xff1a; 今天在看到这个教程的时候&#xff0c;在自己的电…...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

ubuntu22.04 安装docker 和docker-compose

首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...