当前位置: 首页 > 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…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

Java入门学习详细版(一)

大家好&#xff0c;Java 学习是一个系统学习的过程&#xff0c;核心原则就是“理论 实践 坚持”&#xff0c;并且需循序渐进&#xff0c;不可过于着急&#xff0c;本篇文章推出的这份详细入门学习资料将带大家从零基础开始&#xff0c;逐步掌握 Java 的核心概念和编程技能。 …...

Windows安装Miniconda

一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

适应性Java用于现代 API:REST、GraphQL 和事件驱动

在快速发展的软件开发领域&#xff0c;REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名&#xff0c;不断适应这些现代范式的需求。随着不断发展的生态系统&#xff0c;Java 在现代 API 方…...

6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础

第三周 Day 3 &#x1f3af; 今日目标 理解类&#xff08;class&#xff09;和对象&#xff08;object&#xff09;的关系学会定义类的属性、方法和构造函数&#xff08;init&#xff09;掌握对象的创建与使用初识封装、继承和多态的基本概念&#xff08;预告&#xff09; &a…...