引言

在应用程序中,日志记录是一个至关重要的功能。不仅有助于调试和监控应用程序,还能帮助我们了解应用程序的运行状态。

在这个示例中将展示如何实现一个自定义的日志记录器,先说明一下,这个实现和Microsoft.Extensions.LoggingSerilogNLog什么的无关,这里只是将自定义的日志数据存入数据库中,或许你也可以理解为实现的是一个存数据的“Repository”,只不过用这个Repository来存的是日志。这个实现包含一个抽象包和两个实现包,两个实现分别是用 EntityFramework Core 和 MySqlConnector 。日志记录操作将放在本地队列中异步处理,以确保不影响业务处理。

1. 抽象包

1.1 定义日志记录接口

首先,我们需要定义一个日志记录接口 ICustomLogger,它包含两个方法:LogReceived 和 LogProcessed。LogReceived 用于记录接收到的日志,LogProcessed 用于更新日志的处理状态。

namespace Logging.Abstractions;

public interface ICustomLogger
{
/// <summary>
/// 记录一条日志
/// </summary>
void LogReceived(CustomLogEntry logEntry); /// <summary>
/// 根据Id更新这条日志
/// </summary>
void LogProcessed(string logId, bool isSuccess);
}

定义一个日志结构实体CustomLogEntry,用于存储日志的详细信息:

namespace Logging.Abstractions;

public class CustomLogEntry
{
/// <summary>
/// 日志唯一Id,数据库主键
/// </summary>
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Message { get; set; } = default!;
public bool IsSuccess { get; set; }
public DateTime CreateTime { get; set; } = DateTime.UtcNow;
public DateTime? UpdateTime { get; set; } = DateTime.UtcNow;
}

1.2 定义日志记录抽象类

接下来,定义一个抽象类CustomLogger,它实现了ICustomLogger接口,并提供了日志记录的基本功能,将日志写入操作(插入or更新)放在本地队列总异步处理。使用ConcurrentQueue来确保线程安全,并开启一个后台任务异步处理这些日志。这个抽象类只负责将日志写入命令放到队列中,实现类负责消费队列中的消息,确定日志应该怎么写?往哪里写?这个示例中后边会有两个实现,一个是基于EntityFramework Core的实现,另一个是MySqlConnector的实现。

封装一下日志写入命令

namespace Logging.Abstractions;

public class WriteCommand(WriteCommandType commandType, CustomLogEntry logEntry)
{
public WriteCommandType CommandType { get; } = commandType;
public CustomLogEntry LogEntry { get; } = logEntry;
} public enum WriteCommandType
{
/// <summary>
/// 插入
/// </summary>
Insert, /// <summary>
/// 更新
/// </summary>
Update
}

CustomLogger实现

using System.Collections.Concurrent;
using Microsoft.Extensions.Logging; namespace Logging.Abstractions; public abstract class CustomLogger : ICustomLogger, IDisposable, IAsyncDisposable
{
protected ILogger<CustomLogger> Logger { get; } protected ConcurrentQueue<WriteCommand> WriteQueue { get; } protected Task WriteTask { get; }
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly CancellationToken _cancellationToken; protected CustomLogger(ILogger<CustomLogger> logger)
{
Logger = logger; WriteQueue = new ConcurrentQueue<WriteCommand>();
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
WriteTask = Task.Factory.StartNew(TryWriteAsync, _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
} public void LogReceived(CustomLogEntry logEntry)
{
WriteQueue.Enqueue(new WriteCommand(WriteCommandType.Insert, logEntry));
} public void LogProcessed(string messageId, bool isSuccess)
{
var logEntry = GetById(messageId);
if (logEntry == null)
{
return;
} logEntry.IsSuccess = isSuccess;
logEntry.UpdateTime = DateTime.UtcNow;
WriteQueue.Enqueue(new WriteCommand(WriteCommandType.Update, logEntry));
} private async Task TryWriteAsync()
{
try
{
while (!_cancellationToken.IsCancellationRequested)
{
if (WriteQueue.IsEmpty)
{
await Task.Delay(1000, _cancellationToken);
continue;
} if (WriteQueue.TryDequeue(out var writeCommand))
{
await WriteAsync(writeCommand);
}
} while (WriteQueue.TryDequeue(out var remainingCommand))
{
await WriteAsync(remainingCommand);
}
}
catch (OperationCanceledException)
{
// 任务被取消,正常退出
}
catch (Exception e)
{
Logger.LogError(e, "处理待写入日志队列异常");
}
} protected abstract CustomLogEntry? GetById(string messageId); protected abstract Task WriteAsync(WriteCommand writeCommand); public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
} public async ValueTask DisposeAsync()
{
await DisposeAsyncCore();
Dispose(false);
GC.SuppressFinalize(this);
} protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_cancellationTokenSource.Cancel();
try
{
WriteTask.Wait();
}
catch (AggregateException ex)
{
foreach (var innerException in ex.InnerExceptions)
{
Logger.LogError(innerException, "释放资源异常");
}
}
finally
{
_cancellationTokenSource.Dispose();
}
}
} protected virtual async Task DisposeAsyncCore()
{
_cancellationTokenSource.Cancel();
try
{
await WriteTask;
}
catch (Exception e)
{
Logger.LogError(e, "释放资源异常");
}
finally
{
_cancellationTokenSource.Dispose();
}
}
}

1.3 表结构迁移

为了方便表结构迁移,我们可以使用FluentMigrator.Runner.MySql,在项目中引入:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup> <ItemGroup>
<PackageReference Include="FluentMigrator.Runner.MySql" Version="6.2.0" />
</ItemGroup>
</Project>

新建一个CreateLogEntriesTable,放在Migrations目录下

[Migration(20241216)]
public class CreateLogEntriesTable : Migration
{
public override void Up()
{
Create.Table("LogEntries")
.WithColumn("Id").AsString(36).PrimaryKey()
.WithColumn("Message").AsCustom(text)
.WithColumn("IsSuccess").AsBoolean().NotNullable()
.WithColumn("CreateTime").AsDateTime().NotNullable()
.WithColumn("UpdateTime").AsDateTime();
} public override void Down()
{
Delete.Table("LogEntries");
}
}

添加服务注册

using FluentMigrator.Runner;
using Logging.Abstractions;
using Logging.Abstractions.Migrations; namespace Microsoft.Extensions.DependencyInjection; public static class CustomLoggerExtensions
{
/// <summary>
/// 添加自定义日志服务表结构迁移
/// </summary>
/// <param name="services"></param>
/// <param name="connectionString">数据库连接字符串</param>
/// <returns></returns>
public static IServiceCollection AddCustomLoggerMigration(this IServiceCollection services, string connectionString)
{
services.AddFluentMigratorCore()
.ConfigureRunner(
rb => rb.AddMySql5()
.WithGlobalConnectionString(connectionString)
.ScanIn(typeof(CreateLogEntriesTable).Assembly)
.For.Migrations()
)
.AddLogging(lb =>
{
lb.AddFluentMigratorConsole();
}); using var serviceProvider = services.BuildServiceProvider();
using var scope = serviceProvider.CreateScope();
var runner = scope.ServiceProvider.GetRequiredService<IMigrationRunner>();
runner.MigrateUp(); return services;
}
}

2. EntityFramework Core 的实现

2.1 数据库上下文

新建Logging.EntityFrameworkCore项目,添加对Logging.Abstractions项目的引用,并在项目中安装Pomelo.EntityFrameworkCore.MySqlMicrosoft.Extensions.ObjectPool

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.11" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
</ItemGroup> <ItemGroup>
<ProjectReference Include="..\Logging.Abstractions\Logging.Abstractions.csproj" />
</ItemGroup> </Project>

创建CustomLoggerDbContext类,用于管理日志实体

using Logging.Abstractions;
using Microsoft.EntityFrameworkCore; namespace Logging.EntityFrameworkCore; public class CustomLoggerDbContext(DbContextOptions<CustomLoggerDbContext> options) : DbContext(options)
{
public virtual DbSet<CustomLogEntry> LogEntries { get; set; }
}

使用 ObjectPool 管理 DbContext:提高性能,减少 DbContext 的创建和销毁开销。

创建CustomLoggerDbContextPoolPolicy

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.ObjectPool; namespace Logging.EntityFrameworkCore; /// <summary>
/// DbContext 池策略
/// </summary>
/// <param name="options"></param>
public class CustomLoggerDbContextPoolPolicy(DbContextOptions<CustomLoggerDbContext> options) : IPooledObjectPolicy<CustomLoggerDbContext>
{
/// <summary>
/// 创建 DbContext
/// </summary>
/// <returns></returns>
public CustomLoggerDbContext Create()
{
return new CustomLoggerDbContext(options);
} /// <summary>
/// 回收 DbContext
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public bool Return(CustomLoggerDbContext context)
{
// 重置 DbContext 状态
context.ChangeTracker.Clear();
return true;
}
}

2.2 实现日志写入

创建一个EfCoreCustomLogger,继承自CustomLogger,实现日志写入的具体逻辑

using Logging.Abstractions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.ObjectPool; namespace Logging.EntityFrameworkCore; /// <summary>
/// EfCore自定义日志记录器
/// </summary>
public class EfCoreCustomLogger(ObjectPool<CustomLoggerDbContext> contextPool, ILogger<EfCoreCustomLogger> logger) : CustomLogger(logger)
{
/// <summary>
/// 根据Id查询日志
/// </summary>
/// <param name="logId"></param>
/// <returns></returns>
protected override CustomLogEntry? GetById(string logId)
{
var dbContext = contextPool.Get();
try
{
return dbContext.LogEntries.Find(logId);
}
finally
{
contextPool.Return(dbContext);
}
} /// <summary>
/// 写入日志
/// </summary>
/// <param name="writeCommand"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
protected override async Task WriteAsync(WriteCommand writeCommand)
{
var dbContext = contextPool.Get(); try
{
switch (writeCommand.CommandType)
{
case WriteCommandType.Insert:
if (writeCommand.LogEntry != null)
{
await dbContext.LogEntries.AddAsync(writeCommand.LogEntry);
} break;
case WriteCommandType.Update:
{
if (writeCommand.LogEntry != null)
{
dbContext.LogEntries.Update(writeCommand.LogEntry);
} break;
}
default:
throw new ArgumentOutOfRangeException();
} await dbContext.SaveChangesAsync();
}
finally
{
contextPool.Return(dbContext);
}
}
}

添加服务注册

using Logging.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool; namespace Logging.EntityFrameworkCore; public static class EfCoreCustomLoggerExtensions
{
public static IServiceCollection AddEfCoreCustomLogger(this IServiceCollection services, string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentNullException(nameof(connectionString));
} services.AddCustomLoggerMigration(connectionString); services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.AddSingleton(serviceProvider =>
{
var options = new DbContextOptionsBuilder<CustomLoggerDbContext>()
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
.Options;
var poolProvider = serviceProvider.GetRequiredService<ObjectPoolProvider>();
return poolProvider.Create(new CustomLoggerDbContextPoolPolicy(options));
}); services.AddSingleton<ICustomLogger, EfCoreCustomLogger>(); return services;
}
}

3. MySqlConnector 的实现

MySqlConnector 的实现比较简单,利用原生SQL操作数据库完成日志的插入和更新。

新建Logging.Connector项目,添加对Logging.Abstractions项目的引用,并安装MySqlConnector

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup> <ItemGroup>
<PackageReference Include="MySqlConnector" Version="2.4.0" />
</ItemGroup> <ItemGroup>
<ProjectReference Include="..\Logging.Abstractions\Logging.Abstractions.csproj" />
</ItemGroup> </Project>

3.1 SQL脚本

为了方便维护,我们把需要用到的SQL脚本放在一个Consts类中

namespace Logging.MySqlConnector;

public class Consts
{
/// <summary>
/// 插入日志
/// </summary>
public const string InsertSql = """
INSERT INTO `LogEntries` (`Id`, `TranceId`, `BizType`, `Body`, `Component`, `MsgType`, `Status`, `CreateTime`, `UpdateTime`, `Remark`)
VALUES (@Id, @TranceId, @BizType, @Body, @Component, @MsgType, @Status, @CreateTime, @UpdateTime, @Remark);
"""; /// <summary>
/// 更新日志
/// </summary>
public const string UpdateSql = """
UPDATE `LogEntries` SET `Status` = @Status, `UpdateTime` = @UpdateTime
WHERE `Id` = @Id;
"""; /// <summary>
/// 根据Id查询日志
/// </summary>
public const string QueryByIdSql = """
SELECT `Id`, `TranceId`, `BizType`, `Body`, `Component`, `MsgType`, `Status`, `CreateTime`, `UpdateTime`, `Remark`
FROM `LogEntries`
WHERE `Id` = @Id;
""";
}

3.2 实现日志写入

创建MySqlConnectorCustomLogger类,实现日志写入的具体逻辑

using Logging.Abstractions;
using Microsoft.Extensions.Logging;
using MySqlConnector; namespace Logging.MySqlConnector; /// <summary>
/// 使用 MySqlConnector 实现记录日志
/// </summary>
public class MySqlConnectorCustomLogger : CustomLogger
{ /// <summary>
/// 数据库连接字符串
/// </summary>
private readonly string _connectionString; /// <summary>
/// 构造函数
/// </summary>
/// <param name="connectionString">MySQL连接字符串</param>
/// <param name="logger"></param>
public MySqlConnectorCustomLogger(
string connectionString,
ILogger<MySqlConnectorCustomLogger> logger)
: base(logger)
{
_connectionString = connectionString;
} /// <summary>
/// 根据Id查询日志
/// </summary>
/// <param name="messageId"></param>
/// <returns></returns>
protected override CustomLogEntry? GetById(string messageId)
{
using var connection = new MySqlConnection(_connectionString);
connection.Open(); using var command = new MySqlCommand(Consts.QueryByIdSql, connection);
command.Parameters.AddWithValue("@Id", messageId); using var reader = command.ExecuteReader();
if (!reader.Read())
{
return null;
} return new CustomLogEntry
{
Id = reader.GetString(0),
Message = reader.GetString(1),
IsSuccess = reader.GetBoolean(2),
CreateTime = reader.GetDateTime(3),
UpdateTime = reader.GetDateTime(4)
};
} /// <summary>
/// 处理日志
/// </summary>
/// <param name="writeCommand"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
protected override async Task WriteAsync(WriteCommand writeCommand)
{
await using var connection = new MySqlConnection(_connectionString);
await connection.OpenAsync(); switch (writeCommand.CommandType)
{
case WriteCommandType.Insert:
{
if (writeCommand.LogEntry != null)
{
await using var command = new MySqlCommand(Consts.InsertSql, connection);
command.Parameters.AddWithValue("@Id", writeCommand.LogEntry.Id);
command.Parameters.AddWithValue("@Message", writeCommand.LogEntry.Message);
command.Parameters.AddWithValue("@IsSuccess", writeCommand.LogEntry.IsSuccess);
command.Parameters.AddWithValue("@CreateTime", writeCommand.LogEntry.CreateTime);
command.Parameters.AddWithValue("@UpdateTime", writeCommand.LogEntry.UpdateTime);
await command.ExecuteNonQueryAsync();
} break;
}
case WriteCommandType.Update:
{
if (writeCommand.LogEntry != null)
{
await using var command = new MySqlCommand(Consts.UpdateSql, connection);
command.Parameters.AddWithValue("@Id", writeCommand.LogEntry.Id);
command.Parameters.AddWithValue("@IsSuccess", writeCommand.LogEntry.IsSuccess);
command.Parameters.AddWithValue("@UpdateTime", writeCommand.LogEntry.UpdateTime);
await command.ExecuteNonQueryAsync();
} break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}

添加服务注册

using Logging.Abstractions;
using Logging.MySqlConnector;
using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.DependencyInjection; /// <summary>
/// MySqlConnector 日志记录器扩展
/// </summary>
public static class MySqlConnectorCustomLoggerExtensions
{
/// <summary>
/// 添加 MySqlConnector 日志记录器
/// </summary>
/// <param name="services"></param>
/// <param name="connectionString"></param>
/// <returns></returns>
public static IServiceCollection AddMySqlConnectorCustomLogger(this IServiceCollection services, string connectionString)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentNullException(nameof(connectionString));
} services.AddSingleton<ICustomLogger>(s =>
{
var logger = s.GetRequiredService<ILogger<MySqlConnectorCustomLogger>>();
return new MySqlConnectorCustomLogger(connectionString, logger);
});
services.AddCustomLoggerMigration(connectionString); return services;
}
}

4. 使用示例

下边是一个EntityFramework Core的实现使用示例,MySqlConnector的使用方式相同。

新建WebApi项目,添加Logging.ntityFrameworkCore

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); // 添加EntityFrameworkCore日志记录器
var connectionString = builder.Configuration.GetConnectionString("MySql");
builder.Services.AddEfCoreCustomLogger(connectionString!); var app = builder.Build(); // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
} app.UseAuthorization(); app.MapControllers(); app.Run();

在控制器中使用

namespace EntityFrameworkCoreTest.Controllers;

[ApiController]
[Route("[controller]")]
public class TestController(ICustomLogger customLogger) : ControllerBase
{
[HttpPost("InsertLog")]
public IActionResult Post(CustomLogEntry model)
{
customLogger.LogReceived(model); return Ok();
} [HttpPut("UpdateLog")]
public IActionResult Put(string messageId, MessageStatus status)
{
customLogger.LogProcessed(messageId, status); return Ok();
}
}

使用 .NET Core 实现一个自定义日志记录器的更多相关文章

  1. 基于.NetCore3.1系列 —— 日志记录之自定义日志组件

    一.前言 回顾:日志记录之日志核心要素揭秘 在上一篇中,我们通过学习了解在.net core 中内置的日志记录中的几大核心要素,在日志工厂记录器(ILoggerFactory)中实现将日志记录提供器( ...

  2. ASP.NET Core搭建多层网站架构【7-使用NLog日志记录器】

    2020/01/29, ASP.NET Core 3.1, VS2019, NLog.Web.AspNetCore 4.9.0 摘要:基于ASP.NET Core 3.1 WebApi搭建后端多层网站 ...

  3. .net core Blazor+自定义日志提供器实现实时日志查看器

    场景 我们经常远程连接服务器去查看日志,比较麻烦,如果直接访问项目的某个页面就能实时查看日志就比较奈斯了,花了1天研究了下.net core 日志的原理,结合blazor实现了基本效果. 实现原理 自 ...

  4. 如果你想深刻理解ASP.NET Core请求处理管道,可以试着写一个自定义的Server

    我们在上面对ASP.NET Core默认提供的具有跨平台能力的KestrelServer进行了详细介绍(<聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer& ...

  5. asp.net core mcroservices 架构之 分布式日志(二)之自定义日志开发

    netcore日志原理 netcore的日志是作为一个扩展库存在的,每个组件都有它的入口,那么作为研究这个组件的入口是最好的,首先看两种方式: 这个是源码例子提供的. var loggingConfi ...

  6. Serilog 是 ASP.NET Core 的一个插件,可以简化日志记录

    [翻译] ASP.NET Core 利用 Docker.ElasticSearch.Kibana 来记录日志 原文: Logging with ElasticSearch, Kibana, ASP.N ...

  7. 自定义日志阅读器——包括了一个load取Tomcat日志的分析器

    最近在写往公司产品里添加Tomcat适配器,以支持Tomcat.有一些功能需要摘取到Tomcat的部分日志.没有合适的工具,也不想去网上找了,就自己写了一个. 简单的画了一下设计方案: 下面直接上代码 ...

  8. Asp.Net Core中简单使用日志组件log4net

    本文将简单介绍在.NET 6中使用log4net的方法,具体见下文范例. 1.首先新建一个ASP.NET Core空项目 2.通过Nuget包管理器安装下面两个包 log4net Microsoft. ...

  9. ASP.NET Core MVC之Serilog日志处理,你了解多少?

    前言 本节我们来看看ASP.NET Core MVC中比较常用的功能,对于导入和导出目前仍在探索中,项目需要自定义列合并,所以事先探索了如何在ASP.NET Core MVC进行导入.导出,更高级的内 ...

  10. 【PHP系列】PHP推荐标准之PSR-3,日志记录器接口

    上节聊完了PHP官方的相关代码规范,下面给大家带来了PHP系列的PHP推荐标准的另外两个,PSR-3,PSR-4. 首先,我们先来了解下PSR-3是怎么回事. PHP-FIG发布的第三个推荐规范与前两 ...

随机推荐

  1. Java以封装对象的方式读取CSV文件存储数据库

    依赖 <!-- https://mvnrepository.com/artifact/net.sourceforge.javacsv/javacsv --> <dependency& ...

  2. SD卡的基本知识与选购指南

    1.SD卡与TF卡 SD 卡:又叫标准 SD 卡,其尺寸大小为 32 x 24 x 2.1 mm ,一般用于数码相机.声卡和采集卡等设备. TF 卡:又叫 micro SD 卡,其尺寸大小为 15 x ...

  3. yaml.load与yaml.dump的用法

    import yaml #向yaml文件中写 with open("E:\个人\ rename.yaml", 'w') as f: project = {'在远方':"1 ...

  4. 墨天轮最受DBA欢迎的数据库技术文档-容灾备份篇

    在大家的支持与认可下,墨天轮编辑部继续为大家整理出了[墨天轮最受欢迎的技术文档]系列第二篇--容灾备份篇,希望能够帮助到大家. 作为一名数据库管理员,最怕数据库中心突然失去服务能力.影响业务,而不论是 ...

  5. select语句

    SELECT语句可以从表中选择数据 SELECT <列名1>,<列名2> as "aaa" FROM <表名>; 查询两列数据SELECT * ...

  6. Top100题(上)

    Top100(上) 散列 1. 两数之和 struct MyListNode { int val; int pos; struct MyListNode *next; }; // 散列表 typede ...

  7. MVC PHP架构 博客论坛实现全过程

    目录 1. MVC的历史 1.1 优点与缺点 1.1.1 优点 1.1.2 缺点 2. 个人博客论坛的MVC实现 2.1 前言 2.2 web代码结构 框架 2.2.1 web应用发展 2.2.2 C ...

  8. vue之JavaScript封装

    导入js文件有很多种方式,我喜欢使用这种,感觉跟框架导入很相似.没有多余的沉湎. 定义一个js文件,比如util.js import Vue from "vue"; //===== ...

  9. python处理大量数据excel表格中间格式神器pickle.pkl文件操作说明

    读取写入千万级别的excel文件费时费力,调试起来比较慢,面对这个问题,第一步可以先无脑全部转换成pkl文件,这样几乎和内存操作一样的速度. 例如: t=pd.read_excel("12月 ...

  10. 4.7 Linux压缩文件或目录中文件为.bz2格式(bzip2命令)

    bzip2 命令同 gzip 命令类似,只能对文件进行压缩(或解压缩),对于目录只能压缩(或解压缩)该目录及子目录下的所有文件.当执行压缩任务完成后,会生成一个以".bz2"为后缀 ...