基于 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 实现接口调用日志记录
🎯导读:mzt-biz-log 是一个用于记录操作日志的通用组件,旨在追踪系统中“谁”在“何时”对“何事”执行了“何种操作”。该组件通过简单的注解配置,如 LogRecord,即可实现接口调用的日志记录,支持成功与失败…...
docker容器的安装以及用法
1、了解docker 1.1、docker是什么 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现…...
Java中的Consumer接口应该如何使用(通俗易懂图解)
应用场景: 第一次程序员A写好了个基础的遍历方法: public class Demo1 {public static void main(String[] args) {//假设main方法为程序员B写的,此时需要去调用A写好的一个遍历方法//1.如果此时B突然发现想将字符串以小写的形式打印出来,则…...
D102【python 接口自动化学习】- pytest进阶之fixture用法
day102 pytest的usefixtures方法 学习日期:20241219 学习目标:pytest基础用法 -- pytest的usefixtures方法 学习笔记: fixture调用方法 实际应用 总结 pytest.mark.usefixtures(func),pytest的usefixtures方法,无…...
从零玩转CanMV-K230(4)-小核Linux驱动开发参考
前言 K230 芯片是一款基于 RISC-V 架构的端侧 AIoT 芯片,包含两个核心: CPU 1: RISC-V 处理器,1.6GHz,32KB I-cache, 32KB D-cache, 256KB L2 Cache,128bit RVV 1.0扩展 CPU 0: RISC-V 处理器,0.8GHz&am…...
上汽大通汽车CAN数据解析开发服务及技术商用领域详细解析
上汽大通G90是一款集豪华、科技与舒适于一身的中大型MPV,号称“国产埃尔法”。在国内市场,作为“卷王”的G90主要面向中大型MPV市场,满足家庭出行、商务接待和客运租赁等多元化场景需求,在国内市场上取得了不错的销售成绩。在海外…...
基于SCUI的后台管理系统
一、SCUI Admin 官方地址:https://python-abc.xyz/scui-doc/ 高性能中后台前端解决方案,基于 Vue3、elementPlus 持续性的提供独家组件和丰富的业务模板帮助你快速搭建企业级中后台前端任务。 预览地址:https://python-abc.xyz/scui-doc/de…...
使用频谱分析仪:RBW,Res BW,分辨率带宽;Sweep,扫描;noise floor,底噪,如何降低底噪?
RBW与Sweep的定义及其特性阐述: Res BW,即Resolution Bandwidth(分辨率带宽),是衡量仪器分辨信号细节能力的重要参数。当RBW的数值越小,意味着像素点的尺寸更为精细,从而能够观察到更为细微的信…...
项目管理工具Maven(一)
Maven的概念 什么是Maven 翻译为“专家”,“内行”Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建,依赖管理和项目信息管理。什么是理想的项目构建? 高度自动化,跨平台,可重用的组件,标准…...
阿里云ESC服务器一次性全部迁移到另一个ESC
摘要: 在云计算时代,服务器迁移是企业优化资源配置、提升业务灵活性的常见需求。本文将详细介绍如何将阿里云ECS(Elastic Compute Service)服务器一次性迁移到另一个ECS实例。整个迁移过程分为四个关键步骤: 创建自定义…...
搭建分布式Kafka集群
title: 搭建分布式Kafka集群 date: 2024-12-1 14:00:00 categories: - 服务器 tags: - Kafka - 大数据搭建分布式Kafka集群 在主节点上安装Kafka; Kafka使用Zookeeper服务器来存储元数据信息 本次实验环境:Centos 7-2009、Hadoop-3.1.4、JDK 8、Zookeep…...
【后端面试总结】深入解析进程和线程的区别
在操作系统和并发编程中,进程和线程是两个核心概念。它们各自承担着不同的职责,并在多任务处理中发挥着关键作用。本文将从定义、特性、应用场景以及优缺点等多个方面对进程和线程进行详细对比,帮助读者深入理解它们之间的区别。 一、进程和…...
java版电子招投标采购|投标|评标|竞标|邀标|评审招投标系统源码
招投标管理系统是一款适用于招标代理、政府采购、企业采购和工程交易等领域的企业级应用平台。该平台以项目为主线,从项目立项到项目归档,实现了全流程的高效沟通和协作。通过该平台,用户可以实时共享项目数据信息,实现规范化管理…...
SSM 赋能 Vue 助力:新锐台球厅管理系统的设计与实现的辉煌之路
2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统,它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等,非常适…...
C++ 并发专题 - C++线程同步的几种方法
一:概述 线程同步是多线程编程中的一个重要概念,它用于控制多个线程之间对共享资源的访问,避免竞态条件(race condition)和数据不一致的问题。线程同步确保在多线程环境中,多个线程访问共享数据时能够按照某…...
使用Python脚本进行编写批量根据源IP进行查询的语句用于态势感知攻击行为的搜索
使用Python脚本进行编写批量根据源IP进行查询的语句 以下根据ip-list集里面的IP地址(可以自行扩充),然后采用srcaddress "{ip}" or 的形式进行打印并存储在路径为:桌面的IOC结果.txt --------------------------代码如…...
Python中的zip/unzip:像拉拉链一样组合数据的艺术
今天让我们一起探讨Python中一个优雅而强大的内置功能: zip 和 unzip 。听名字就知道,它就像我们衣服上的拉链一样,能把两边的数据完美地咬合在一起。 从一个有趣的例子开始 想象你正在开发一个班级管理系统。每个学生都有名字、成绩和评语…...
数电课设·简易数字钟(Quartus Ⅱ)
忽如一夜春风来,千树万树梨花开 —— 《白雪歌诵武判官归京》 岑参 【唐】 目录 简易数字钟 要点剖析: 逐步分析: 端口说明: 代码展示: 分部解释: 代码编译结果: 提醒 : …...
大模型中RAG模型的检索过程是如何实现的?(附最佳实践资料)
RAG模型的检索过程主要涉及以下几个步骤: 向量化(Embedding):首先,需要将外部知识库中的文档转换为计算机能够理解的向量形式。这一步骤通常使用预训练的嵌入模型(如BERT、GPT等)将文本转换为高…...
python:用 sklearn.metrics 评价 K-Means 聚类模型
sklearn 的 metrics 模块提供的聚类模型评价指标如下: ARI 评价法(兰德系数): adjusted_rand_score AMI 评价法(相互信息): adjusted_mutual_info_score V-measure 评分 : completeness_score FMI 评价法 : fowlkes_m…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
【分享】推荐一些办公小工具
1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由:大部分的转换软件需要收费,要么功能不齐全,而开会员又用不了几次浪费钱,借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
