基于 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, "瑜伽");

    @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;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何在繁忙的生活中找到自己的节奏?

目录 一、理解生活节奏的重要性 二、分析当前生活节奏 1. 时间分配 2. 心理状态 3. 身体状况 4. 生活习惯 1. 快慢适中 2. 张弛结合 3. 与目标相符 三、掌握调整生活节奏的策略 1. 设定优先级 2. 合理规划时间 3. 学会拒绝与取舍 4. 保持健康的生活方式 5. 留出…

Docker:目录挂载、数据卷(补充二)

Docker&#xff1a;目录挂载、数据卷 1. 挂载2. 卷映射 1. 挂载 -v /app/nghtml:/usr/share/nginx/html /app/nghtml 是外部主机的地址 /usr/share/nginx/html 是内部容器的地址这里启动一个nginx&#xff0c;然后在后台运行时其命令为 (base) ➜ ~ docker run -d -p 80:80 …

新能源汽车大屏可视化第三次数据存储

任务&#xff1a; 将数据存放到temp.csv 链接&#xff1a; 1.排行页面 https://www.dongchedi.com/sales 2.参数页面 https://www.dongchedi.com/auto/params-carIds-x-9824 完善打印&#xff1a; 1. [{‘series_id’: 5952, ‘series_name’: ‘海鸥’, ‘image’: ‘https://…

Three.js资源-模型下载网站

在使用 Three.js 进行 3D 开发时&#xff0c;拥有丰富的模型资源库可以大大提升开发效率和作品质量。以下是一些推荐的 Three.js 模型下载网站&#xff0c;它们提供了各种类型的 3D 模型&#xff0c;适合不同项目需求。无论你是需要逼真的建筑模型&#xff0c;还是简单的几何体…

无人机故障安全模式设计逻辑与技术!

一、设计逻辑 故障检测与识别&#xff1a; 无人机系统需具备实时监测各项关键参数的能力&#xff0c;如电池电量、电机状态、传感器数据等。 当检测到参数异常或超出预设阈值时&#xff0c;系统应能迅速识别故障类型及其严重程度。 故障处理策略&#xff1a; 根据故障类型…

洞察:OpenAI 全球宕机,企业应该如何应对 LLM 的不稳定性?

北京时间12月12日上午&#xff0c;OpenAI证实其聊天机器人ChatGPT正经历全球范围的宕机&#xff0c;ChatGPT、Sora及API受到影响。 OpenAI 更新事故报告称&#xff0c;已查明宕机原因&#xff0c;正努力以最快速度恢复正常服务&#xff0c;并对宕机表示歉意。 此次 OpenAI 故障…

STM32F407ZGT6-UCOSIII笔记2:UCOSIII任务创建实验-Printf 函数卡住 UCOSIII 系统问题解决

今日简单编写熟悉一下UCOSIII系统的任务创建代码&#xff0c;理解一下OS系统&#xff1a; 并发现以及解决了 Printf 函数卡住 UCOSIII 系统问题解决 文章提供测试代码讲解、完整工程下载、测试效果图 目录 文件结构解释&#xff1a; 任务函数文件&#xff1a; 目前各个文件任…

CUDA从入门到精通(三)——CUDA编程示例

CUDA 编程简介 CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由 NVIDIA 提供的一种并行计算平台和编程模型。它允许开发者利用 NVIDIA GPU 的并行计算能力&#xff0c;编写可以在 GPU 上高效运行的代码&#xff0c;从而加速计算密集型任务。 CUDA 通过…

【十进制整数转换为其他进制数——短除形式的贪心算法】

之前写过一篇用贪心算法计算十进制转换二进制的方法&#xff0c;详见&#xff1a;用贪心算法计算十进制数转二进制数&#xff08;整数部分&#xff09;_短除法求二进制-CSDN博客 经过一段时间的研究&#xff0c;本人又发现两个规律&#xff1a; 1、不仅仅十进制整数转二进制可…

舵机SG90详解

舵机&#xff0c;也叫伺服电机&#xff0c;在嵌入式开发中&#xff0c;舵机作为一种常见的运动控制组件&#xff0c;具有广泛的应用。其中&#xff0c;SG90 舵机以其高效、稳定的性能特点&#xff0c;成为了许多工程师和爱好者的首选&#xff0c;无论是航模、云台、机器人、智能…

如何为IntelliJ IDEA配置JVM参数

在使用IntelliJ IDEA进行Java开发时&#xff0c;合理配置JVM参数对于优化项目性能和资源管理至关重要。IntelliJ IDEA提供了两种方便的方式来设置JVM参数&#xff0c;以确保你的应用程序能够在最佳状态下运行。本文将详细介绍这两种方法&#xff1a;通过工具栏编辑配置和通过服…

跌倒数据集,5345张图片, 使用yolo,coco json,voc xml格式进行标注,平均识别率99.5%以上

跌倒数据集&#xff0c;5345张图片&#xff0c; 使用yolo&#xff0c;coco json&#xff0c;voc xml格式进行标注&#xff0c;平均识别率99.5%以上 &#xff0c;可用于某些场景下识别人是否跌倒或摔倒并进行告警。 数据集分割 训练组99&#xff05; 5313图片 有效集0&am…

nods.js之nrm安装及使用

nods.js之nrm安装及使用 一、简介二、安装 nrm与使用三、报错解决 一、简介 nrm 是 Node.js 的一个工具&#xff0c;用于管理和切换 npm 源&#xff08;Registry&#xff09;。它使得在不同的 npm 镜像源之间切换变得非常容易&#xff0c;尤其对于那些经常因为网络问题或速度原…

selenium自动化测试基础知识

目录 一、概念知识 (一)三大核心组件 (二)Selenium 自动化测试的工作原理 (三)Selenium 支持的操作 (四)Selenium 自动化测试的优点 (五)Selenium 自动化测试的缺点 (六)Selenium 自动化测试的应用场景 总结 二、实操例子 使用前提--安装步骤 注意事项 (一)浏览器的…

Cisco Packet Tarcer配置计网实验笔记

文章目录 概要整体架构流程网络设备互连基础拓扑图拓扑说明配置步骤 RIP/OSPF混合路由拓扑图拓扑说明配置步骤 BGP协议拓扑图拓扑说明配置步骤 ACL访问控制拓扑图拓扑说明配置步骤 HSRP冗余网关拓扑图拓扑说明配置步骤 小结 概要 一些环境配置笔记 整体架构流程 网络设备互连…

RNN LSTM Seq2Seq Attention

非端到端&#xff1a; data -》 cleaning -》 feature Engining &#xff08;70%-80%工作 设计特征&#xff09;-》 分类器 -》预测 端到端 End-to-End&#xff1a; data -》 cleaning -》Deep learning&#xff08;表示学习&#xff0c;从数据中学习特征&#xff09; -》…

【AI日记】24.12.17 kaggle 比赛 2-6 | 把做饭看成一种游戏 | 咖喱牛肉

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 工作 参加&#xff1a;kaggle 比赛 Regression with an Insurance Dataset时间&#xff1a;9 小时睡得好很重要 读书 书名&#xff1a;富兰克林自传时间&#xff1a;0.5 小时阅读原因&#xff1a;100 美元纸…

电脑为什么会提示“msvcr120.dll缺失”?“找不到msvcr120.dll文件”要怎么解决?

电脑故障排查指南&#xff1a;揭秘“msvcr120.dll缺失”的真相与解决方案 在软件开发与日常维护的广阔天地里&#xff0c;遇到系统报错或文件缺失的情况可谓家常便饭。今天&#xff0c;我将带领大家深入探讨一个常见的系统提示——“msvcr120.dll缺失”&#xff0c;并揭秘其背…

Kotlin复习

一、Kotlin类型 1.整数 2.浮点 显示转换&#xff1a; 所有数字类型都支持转换为其他类型&#xff0c;但是转换前会检测长度。 toByte(): Byte toShort(): Short toInt(): Int toLong(): Long toFloat(): Float toDouble(): Double 不同进制的数字表示方法&#xff08;为了提高…

【BUG】记一次context canceled的报错

文章目录 案例分析gorm源码解读gin context 生命周期context什么时候cancel的什么时候context会被动cancel掉呢&#xff1f; 野生协程如何处理 案例分析 报错信息 {"L":"ERROR","T":"2024-12-17T11:11:33.0050800","file"…