2024年3月

在.NET中Newtonsoft.Json(Json.NET)是我们常用来进行Json序列化与反序列化的库。

而在使用中常会遇到反序列化Json时,遇到不规则的Json数据解构而抛出异常。

Newtonsoft.Json 支持序列化和反序列化过程中的错误处理。

允许您捕获错误并选择是处理它并继续序列化,还是让错误冒泡并抛出到您的应用程序中。

错误处理是通过两种方法定义的: JsonSerializerSettings 上的ErrorEvent和OnErrorAttribute。

ErrorEvent

下面是个ErrorEvent的例子,下面的例子中我们既能正确反序列化列表中的事件类型,也能捕获其中的错误事件

List<string> errors = new List<string>();

List
<DateTime> c = JsonConvert.DeserializeObject<List<DateTime>>(@"[
'2009-09-09T00:00:00Z',
'I am not a date and will error!',
[
1
],
'1977-02-20T00:00:00Z',
null,
'2000-12-01T00:00:00Z'
]
",newJsonSerializerSettings
{
Error
= delegate(objectsender, ErrorEventArgs args)
{
errors.Add(args.ErrorContext.Error.Message);
args.ErrorContext.Handled
= true;
},
Converters
= { newIsoDateTimeConverter() }
});
//2009-09-09T00:00:00Z//1977-02-20T00:00:00Z//2000-12-01T00:00:00Z

OnErrorAttribute

OnErrorAttribute的工作方式与 Newtonsoft.Json 的其他.NET 序列化属性非常相似。

您只需将该属性放置在采用正确参数的方法上:StreamingContext 和 ErrorContext。方法的名称并不重要。

public classPersonError
{
private List<string>_roles;public string Name { get; set; }public int Age { get; set; }public List<string>Roles
{
get{if (_roles == null)
{
throw new Exception("Roles not loaded!");
}
return_roles;
}
set { _roles =value; }
}
public string Title { get; set; }

[OnError]
internal voidOnError(StreamingContext context, ErrorContext errorContext)
{
errorContext.Handled
= true;
}
}

PersonError person
= newPersonError
{
Name
= "George Michael Bluth",
Age
= 16,
Roles
= null,
Title
= "Mister Manager"};string json =JsonConvert.SerializeObject(person, Formatting.Indented);

Console.WriteLine(json);
//{//"Name": "George Michael Bluth",//"Age": 16,//"Title": "Mister Manager"//}

前言

一站式社交平台数据抓取利器,带你玩转小红书、抖音、快手、B站和微博数据分析

不经意间,来查看MediaCrawler仓库源码,发现作者已经删库了。看来是领奖了。才几天不到的时间Star数量已经直逼10K了,增长速度近乎疯狂。
前两天只是将代码下载下来了,还没认真的玩。还好代码本地已经有了。如果有兴趣的也可以来找我要,免费的哟。

作者在这里也明显进行了标注使用了无头浏览器

那到底什么事无头浏览器呢?

无头浏览器(Headless Browser)是一种浏览器程序,没有图形用户界面(GUI),但能够执行与普通浏览器相似的功能。无头浏览器能够加载和解析网页,执行JavaScript代码,处理网页事件,并提供对DOM(文档对象模型)的访问和操作能力。
与传统浏览器相比,无头浏览器的主要区别在于其没有可见的窗口或用户界面。这使得它在后台运行时,不会显示实际的浏览器窗口,从而节省了系统资源,并且可以更高效地执行自动化任务。
常见的无头浏览器包括Headless Chrome(Chrome的无头模式)、PhantomJS、Puppeteer(基于Chrome的无头浏览器库)、playwright等。它们提供了编程接口,使开发者能够通过代码自动化控制和操作浏览器行为。

如果您比较Playwright和 Puppeteer的贡献者页面 ,您会注意到Puppeteer的前两个贡献者现在在 Playwright 上工作。Puppeteer 团队实质上是从 Google 转移到 Microsoft 并成为 Playwright 团队。

因此,Playwright 在很多方面与 Puppeteer 非常相似。API 方法在大多数情况下是相同的,并且默认情况下 Playwright 还捆绑了兼容的浏览器。

Playwright 最大的区别在于跨浏览器支持。它可以驱动 Chromium、WebKit(Safari 的浏览器引擎)和 Firefox。

无头浏览器其实就是看不见的浏览器,所有的操作都要通过代码调用 API 来控制,所以浏览器能干的事儿,无头浏览器都能干,而且很多事儿做起来比标准的浏览器更简单。

能够干什么呢?

我举几个常用的功能来说明一下无头浏览器的主要使用场景

  • 自动化测试: 无头浏览器可以模拟用户行为,执行自动化测试任务,例如对网页进行加载、表单填写、点击按钮、检查页面元素等。
  • 数据抓取: 无头浏览器可用于爬取网页数据,自动访问网站并提取所需的信息,用于数据分析、搜索引擎优化等。
  • 屏幕截图: 无头浏览器可以加载网页并生成网页的截图,用于生成快照、生成预览图像等。
  • 服务器端渲染: 无头浏览器可以用于服务器端渲染(Server-side Rendering),将动态生成的页面渲染为静态HTML,提供更好的性能和搜索引擎优化效果。
  • 生成 PDF 文件:使用浏览器自带的生成 PDF 功能,将目标页面转换成 PDF 。

这个仓库如何使用呢

首先这是一个Python库,当然要安装必要的Python环境,这个我就不多说了,通过GPT或者在网上搜索都可以进行安装好。
接下来就是拿到代码后如何使用,这个其实作者也是非常的友好了,直接看README。

最后运行命令后,会出现一个二维码,比如这里我测试的是小红书,出现二维码之后,我们用小红书App来扫描,并进行确认身份,这样无头浏览器就能识别并记住我们的身份信息,后续就可以通过该身份进行抓取一些数据了。


这里我配置了一条小红书的笔记ID,这里是个数据可以配置多个的。

好了,OK ,数据就被这么简单的抓取下来了。


作者还特意对数据存储做了封装,如果数据量大的时候存储起来可能就非常的方便了。
如果需求量很大,还可以对其源码进行研究改造,好了今天的学习就到这里了。

在 NVIDIA DGX Cloud上使用 H100 GPU 轻松训练模型

今天,我们正式宣布推出
DGX 云端训练 (Train on DGX Cloud)
服务,这是 Hugging Face Hub 上针对企业 Hub 组织的全新服务。

通过在 DGX 云端训练,你可以轻松借助 NVIDIA DGX Cloud的高速计算基础设施来使用开放的模型。这项服务旨在让企业 Hub 的用户能够通过几次点击,就在
Hugging Face Hub
中轻松访问最新的 NVIDIA H100 Tensor Core GPU,并微调如 Llama、Mistral 和 Stable Diffusion 这样的流行生成式 AI (Generative AI) 模型。

GPU 不再是稀缺资源

这一新体验基于我们去年宣布的
战略合作
,旨在简化 NVIDIA 加速计算平台上开放生成式 AI 模型的训练和部署。开发者和机构面临的主要挑战之一是 GPU 资源稀缺,以及编写、测试和调试 AI 模型训练脚本的工作繁琐。在 DGX 云上训练为这些挑战提供了简便的解决方案,提供了对 NVIDIA GPUs 的即时访问,从 NVIDIA DGX Cloud上的 H100 开始。此外,该服务还提供了一个简洁的无代码训练任务创建体验,由 Hugging Face AutoTrain 和 Hugging Face Spaces 驱动。

通过
企业版的 HF Hub
,组织能够为其团队提供强大 NVIDIA GPU 的即时访问权限,只需按照训练任务所用的计算实例分钟数付费。

在 DGX 云端训练是目前训练生成式 AI 模型最简单、最快速、最便捷的方式,它结合了强大 GPU 的即时访问、按需付费和无代码训练,这对全球的数据科学家来说将是一次变革性的进步!

—— Abhishek Thakur, Hugging Face AutoTrain 团队创始人

今天发布的 Hugging Face Autotrain,得益于 DGX 云的支持,标志着简化 AI 模型训练过程向前迈出了重要一步,通过将 NVIDIA 的云端 AI 超级计算机与 Hugging Face 的友好界面结合起来,我们正在帮助各个组织加速他们的 AI 创新步伐。

—— Alexis Bjorlin, NVIDIA DGX Cloud 副总裁

操作指南

在 NVIDIA DGX Cloud 上训练 Hugging Face 模型变得非常简单。以下是针对如何微调 Mistral 7B 的分步教程。

注意:你需要访问一个拥有
企业版的 HF Hub
订阅的组织账户,才能使用在 DGX 云端训练的服务

你可以在支持的生成式 AI 模型的模型页面上找到在 DGX 云端训练的选项。目前,它支持以下模型架构:Llama、Falcon、Mistral、Mixtral、T5、Gemma、Stable Diffusion 和 Stable Diffusion XL。

点击“训练 (Train)”菜单,并选择“NVIDIA DGX Cloud”选项,这将打开一个页面,让你可以选择你的企业组织。

接下来,点击“Create new Space”。当你首次使用在 DGX 云端训练时,系统将在你的组织内创建一个新的 Hugging Face 空间,使你可以利用 AutoTrain 创建将在 NVIDIA DGX Cloud上执行的训练任务。当你日后需要创建更多训练任务时,系统将自动将你重定向到已存在的 AutoTrain Space 应用。

进入 AutoTrain Space 应用后,你可以通过配置硬件、基础模型、任务和训练参数来设置你的训练任务。

在硬件选择方面,你可以选择 NVIDIA H100 GPUs,提供 1x、2x、4x 和 8x 实例,或即将推出的 L40S GPUs。训练数据集需要直接上传至“上传训练文件”区域,目前支持 CSV 和 JSON 文件格式。请确保根据以下示例正确设置列映射。对于训练参数,你可以直接在右侧的 JSON 配置中进行编辑,例如,将训练周期数从 3 调整为 2。

一切设置完成后,点击“开始训练”即可启动你的训练任务。AutoTrain 将验证你的数据集,并请求你确认开始训练。

你可以通过查看这个 Space 应用的“Logs 日志”来查看训练进度。

训练完成后,你微调后的模型将上传到 Hugging Face Hub 上你所选择的命名空间内的一个新的私有仓库中。

从今天起,所有企业 Hub 组织都可以使用在 DGX 云端训练的服务了!欢迎尝试并分享你的反馈!

DGX 云端训练的定价

使用在 DGX 云端训练服务,将根据你训练任务期间使用的 GPU 实例分钟数来计费。当前的训练作业价格为:H100 实例每 GPU 小时 8.25 美元,L40S 实例每 GPU 小时 2.75 美元。作业完成后,费用将累加到你企业 Hub 组织当前的月度账单中。你可以随时查看企业 Hub 组织的计费设置中的当前和历史使用情况。

例如,微调 1500 个样本的 Mistral 7B 在一台 NVIDIA L40S 上大约需要 10 分钟,成本约为 0.45 美元。

我们的旅程刚刚开始

我们很高兴能与 NVIDIA 合作,推动加速机器学习在开放科学、开源和云服务领域的普惠化。

通过
BigCode
项目的合作,我们训练了
StarCoder 2 15B
,这是一个基于超过 600 种编程语言训练的全开放、最先进的代码大语言模型(LLM)。

我们在开源方面的合作推动了新的
optimum-nvidia 库
的开发,加速了最新 NVIDIA GPUs 上大语言模型的推理,已经达到了 Llama 2 每秒 1200 Tokens 的推理速度。

我们在云服务方面的合作促成了今天的在 DGX 云端训练服务。我们还在与 NVIDIA 合作优化推理过程,并使加速计算对 Hugging Face 社区更容易受益。此外,Hugging Face 上一些最受欢迎的开放模型将出现在今天 GTC 上宣布的
NVIDIA NIM 微服务
上。

本周参加 GTC 的朋友们,请不要错过周三 3/20 下午 3 点 PT 的会议
S63149

Jeff
将带你深入了解在 DGX 云端训练等更多内容。另外,不要错过下一期 Hugging Cast,在那里我们将现场演示在 DGX 云端训练,并且你可以直接向
Abhishek

Rafael
提问,时间是周四 3/21 上午 9 点 PT / 中午 12 点 ET / 17h CET -
请在此注册

XSS 从 PDF 中窃取数据

将服务器端 XSS 注入到动态生成的 PDF 中

在 hack the box 的
Book
机器
(Scripting Track)
上,我遇到了一个 Web 应用程序,它使用用户控制的输入来生成 PDF 文件。用户输入输入,下载时该输入将呈现为 PDF 文件。

我从阅读许多文章中意识到与动态生成的 PDF 相关的
XSS 和 SSRF
漏洞,但直到我遇到
Book
机器才自己尝试过。


当我每次单击PDF链接时都看到下载功能生成PDF文件时,我开始再次搜索与此漏洞相关的漏洞赏金文章,以刷新我对如何利用它

前言

最近一直在写 Go 和 Python ,好久没写 C# ,重新回来写 C# 代码时竟有一种亲切感~

说回正题。

在当今这个数字化迅速发展的时代,每一个操作都可能对业务产生深远的影响,无论是对数据的简单查询,还是对系统配置的修改。在这样的背景下,审计日志不仅仅是一种遵循最佳实践的手段,更是确保数据安全、提高系统透明度、促进责任归属明晰的关键工具。通过详细记录谁在何时对系统进行了何种操作,审计日志帮助组织追踪用户活动,分析系统问题,甚至在发生安全事件时,提供必要的线索进行调查。

实现审计日志的方法多样,但如何在不干扰主业务逻辑的同时,高效地集成这一功能,是开发者们面临的一大挑战。本文着重探讨如何借鉴面向切面编程(Aspect-Oriented Programming, AOP)的设计思想,在ASP.NET Core应用中以最小化代码侵入性实现动态审计日志功能。AOP允许我们通过预定义的模式,如日志记录、性能统计和安全控制,以声明的方式增强代码功能,而无需修改实际的业务逻辑代码。

本文将指导读者从概念的理解到具体的实施,再到最终的数据持久化处理,特别是如何利用MongoDB这一强大的NoSQL数据库来持久化审计日志数据。无论你是刚刚接触ASP.NET Core的新手,还是寻求为现有项目增加审计功能的资深开发者,本文都将提供从理论到实践的全面指导。通过本文,你将学习到如何设计和实现一个灵活、可扩展的审计日志系统,同时保持对主业务逻辑的最小化干扰。

让我们开始这一旅程,一步步探索如何在ASP.NET Core应用中集成高效、灵活的审计日志机制,利用AOP设计思想实现高度解耦和动态增强的系统功能。

审计日志基础

定义和用途

审计日志有助于追踪用户的操作行为、数据变更记录以及系统的安全性分析等。

常用的审计日志有这些类型。

  • 操作审计
    :记录用户对系统的所有操作,例如登录、登出、数据增删改查等。
  • 数据审计
    :记录数据的变更详情,如记录数据修改前后的值。
  • 安全审计
    :记录安全相关事件,如失败的登录尝试、权限变更等。
  • 性能审计
    :记录关键操作的性能数据,帮助分析系统瓶颈。

本文的代码以实现操作审计为例。

模型定义&关键信息

审计日志是系统安全和管理的关键部分,它帮助我们理解系统内发生了什么、何时发生、由谁触发。为了实现这一目标,审计日志记录需要包含几个关键的组成部分。

  • EventId
    是每条审计记录的唯一标识符。就像每个人都有一个独一无二的身份证号一样,每条审计日志也有一个独特的EventId。这使我们能够轻松地找到和引用特定的审计事件。
  • EventType
    描述了发生的事件类型。这告诉我们这条记录是关于什么的——是用户登录、数据修改,还是权限更改等。通过查看EventType,我们可以快速了解记录的核心信息,而无需深入研究细节。
  • UserId
    是触发事件的用户的标识。在审计日志中记录UserId非常重要,因为它帮助我们追踪谁负责了什么操作。如果发现了问题或者不当行为,我们可以通过UserId来确定责任人。

设计审计日志模型

AuditLog 类

新建
AuditLog.cs
类,每个字段都有注释,我就不再赘述了。

public class AuditLog {
  /// <summary>
  /// 事件唯一标识
  /// </summary>
  public string EventId { get; set; }

  /// <summary>
  /// 事件类型(例如:登录、登出、数据修改等)
  /// </summary>
  public string EventType { get; set; }

  /// <summary>
  /// 执行操作的用户标识
  /// </summary>
  public string UserId { get; set; }

  /// <summary>
  /// 执行操作的用户名
  /// </summary>
  public string Username { get; set; }

  /// <summary>
  /// 事件发生的时间戳
  /// </summary>
  public DateTime Timestamp { get; set; }

  /// <summary>
  /// 用户的IP地址
  /// </summary>
  public string? IPAddress { get; set; }

  /// <summary>
  /// 被操作的实体名称
  /// </summary>
  public string EntityName { get; set; }

  /// <summary>
  /// 被操作的实体标识
  /// </summary>
  public string EntityId { get; set; }

  /// <summary>
  /// 修改前的数据,可根据实际情况以JSON格式存储
  /// </summary>
  public string? OriginalValues { get; set; }

  /// <summary>
  /// 修改后的数据,可根据实际情况以JSON格式存储
  /// </summary>
  public string? CurrentValues { get; set; }

  /// <summary>
  /// 具体的更改内容,可根据实际情况以JSON格式存储
  /// </summary>
  public string? Changes { get; set; }

  /// <summary>
  /// 事件描述
  /// </summary>
  public string? Description { get; set; }
}

捕获审计日志

IAuditLogService 接口

先写一个接口,用来操作审计日志。使用接口可以保持代码的整洁和重用,同时也便于将来对审计日志记录逻辑进行扩展或修改。

为了简单起见,目前这里我们只写了一个记录的方法。

public interface IAuditLogService {
  Task LogAsync(AuditLog auditLog);
}

之后在依赖注入容器里注册(假设实现类的名称为
AuditLogService

builder.Services.AddScope<IAuditLogService, AuditLogService>();

这个设计既保持了代码的清晰与简洁,也为将来可能的需求变更(如改变审计日志的存储方式、增加审计字段等)提供了足够的灵活性。

具体实现会在后续的数据持久化部分介绍。

ActionFilter 方式

在ASP.NET Core中,Action过滤器提供了一种强大的机制,允许我们在控制器的动作执行前后插入自定义逻辑。

我们可以在不修改现有业务逻辑代码的情况下,自动地捕获用户的操作以及数据的更改。这种方式充分利用了AOP的思想,实现了代码的最小化侵入。

创建
AuditLogAttribute

直接上代码了,继承自
ActionFilterAttribute
类,可以实现一个 Action 过滤器的特性,其中
EventType

EntityName
我设计成需要手动指定,其他的属性可以通过各种方法来获取。

public class AuditLogAttribute : ActionFilterAttribute {
  public string EventType { get; set; }
  public string EntityName { get; set; }

  public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) {
    var sp = context.HttpContext.RequestServices;
    var ctxItems = context.HttpContext.Items;

    try {
      var authService = sp.GetRequiredService<AuthService>();

      // 在操作执行前
      var executedContext = await next();

      // 在操作执行后

      // 获取当前用户的身份信息
      var user = await authService.GetUserFromJwt(executedContext.HttpContext.User);

      // 构造AuditLog对象
      var auditLog = new AuditLog {
        EventId = Guid.NewGuid().ToString(),
        EventType = this.EventType,
        UserId = user.UserId,
        Username = user.Username,
        Timestamp = DateTime.UtcNow,
        IPAddress = GetIpAddress(executedContext.HttpContext),
        EntityName = this.EntityName,
        EntityId = ctxItems["AuditLog_EntityId"]?.ToString() ?? "",
        OriginalValues = ctxItems["AuditLog_OriginalValues"]?.ToString(),
        CurrentValues = ctxItems["AuditLog_CurrentValues"]?.ToString(),
        Changes = ctxItems["AuditLog_Changes"]?.ToString(),
        Description = $"操作类型:{this.EventType},实体名称:{this.EntityName}",
      };

      var auditService = sp.GetRequiredService<IAuditLogService>();
      await auditService.LogAsync(auditLog);
    } catch (Exception ex) {
      var logger = sp.GetRequiredService<ILogger<AuditLogAttribute>>();
      logger.LogError(ex, "An error occurred while logging audit information.");
    }
  }
}
注意事项
  • 异常处理
    :考虑到日志记录不应影响主要业务流程的执行,需要添加异常处理逻辑,确保即使日志记录过程中发生异常,也不会干扰到正常的业务逻辑。
  • 性能问题
    :虽然已经在异步方法中记录审计日志,但如果审计日志的记录过程很慢,可能会略微延迟响应时间。可以使用批处理、缓存来异步写入数据库,或者将记录逻辑放到后台任务、消息队列中。

获取IP地址

通过
HttpContext.Connection.RemoteIpAddress
属性可以获取 IP 地址,但如果应用部署在了代理服务器后面(例如使用了负载均衡器),直接获取的IP地址可能是代理服务器的地址,而不是客户端的真实IP地址。

所以这里我封装了
GetIpAddress
方法

private string? GetIpAddress(HttpContext httpContext) {
  // 首先检查X-Forwarded-For头(当应用部署在代理后面时)
  var forwardedFor = httpContext.Request.Headers["X-Forwarded-For"].FirstOrDefault();
  if (!string.IsNullOrWhiteSpace(forwardedFor)) {
    return forwardedFor.Split(',').FirstOrDefault(); // 可能包含多个IP地址
  }

  // 如果没有X-Forwarded-For头,或者需要直接获取连接的远程IP地址
  return httpContext.Connection.RemoteIpAddress?.ToString();
}

首先尝试从
X-Forwarded-For
请求头中获取IP地址,这是一个标准的HTTP头,用于识别通过HTTP代理或负载均衡器发送请求的客户端的原始IP地址。如果请求没有经过代理,或者想要获取代理服务器的地址,那么它会回退到使用
HttpContext.Connection.RemoteIpAddress

X-Forwarded-For
可能包含多个IP地址(如果请求通过多个代理传递),因此代码中使用了
Split(',')
来处理这种情况,并且仅取第一个IP地址作为客户端的真实IP地址。

使用方法

经过封装后可以很方便的使用这个审计功能了,只需要在接口上添加一行代码就可以实现审计功能。

[AuditLog(EventType = nameof(SetSubTaskFeedback), EntityName = nameof(SubTask))]
[HttpPost("sub-tasks/{subId}/set-feedback")]
public async Task<ApiResponse> SetSubTaskFeedback(string subId, [FromBody] SubTaskFeedbackDto dto) {}

手动记录方式

尽管使用Action过滤器是一种高效的自动化方式,但在某些情况下,需要更精细地控制审计日志的记录。这时候只能修改接口代码,在业务逻辑里加入审计日志记录。

这种方式虽然需要直接修改业务代码,但它提供了最大的灵活性和控制能力。

这个代码就没什么特别的了,直接在接口中调用
IAuditLogService

LogAsync
方法来记录审计日志即可。

通过 HttpContext 共享数据

有些参数是很难在 ActionFilter 里自动获取到的,这些往往跟业务逻辑是有关的,这时候 HttpContext 就成为了一个理想的桥梁。

我们可以将一些临时数据,比如操作前的数据快照,存储在
HttpContext.Items
中,然后在过滤器中访问这些数据来完成审计日志的记录。这种方法不仅保持了代码的解耦,还允许我们灵活地在应用的不同部分共享数据。

HttpContext.Items
是一个键值对集合,可用于在一个请求的生命周期内共享数据。

这样在接口中的代码就是

HttpContext.Items["AuditLog_OriginalValues"] = item.FeedbackId;
HttpContext.Items["AuditLog_CurrentValues"] = dto.FeedbackId;
HttpContext.Items["AuditLog_Changes"] = $"更新反馈结果 {item.FeedbackId} -> {dto.FeedbackId}";

注意事项

  • 确保业务逻辑和
    AuditLogAttribute
    中使用的键(如
    AuditLog_OriginalValues
    )唯一且一致,以避免潜在的冲突。这里最好是自己封装一个 class 来提供这些 const ;
  • 如果业务逻辑抽象到了 service 层,则需要注入
    IHttpContextAccessor
    才能访问 HttpContext ,这个服务可以通过
    services.AddHttpContextAccessor()
    来注册;

日志持久化

审计日志的有效持久化是确保长期安全和合规性的关键。

选择存储方案

在选择最合适的存储方案时,需要考虑数据的重要性、查询的频率、成本以及维护的复杂性等多个因素。

关系型数据库(RDS)

关系型数据库,如MySQL、PostgreSQL等,以其稳定性和成熟性受到广泛认可。它们提供了严格的数据完整性保障和复杂查询的强大能力,适合需要执行复杂分析和报告的审计日志。

  • 优点
    :数据结构化、支持复杂查询、成熟的管理工具。
  • 缺点
    :相对较高的成本、可能需要复杂的架构来支持大规模数据。

NoSQL数据库

NoSQL数据库,如MongoDB、Cassandra等,提供了灵活的数据模型和良好的横向扩展能力,适合于结构多变或数据量巨大的审计日志。

  • 优点
    :高可扩展性、灵活的数据模型、快速的写入速度。
  • 缺点
    :查询功能相对有限、数据一致性模型较弱。

文件系统

直接将审计日志写入文件系统是最直接的存储方式,适用于日志量不是特别大或对查询需求不高的场景。

  • 优点
    :实现简单、成本低廉、易于迁移;
  • 缺点
    :查询和分析不便、难以管理大量日志文件、扩展性有限。

每种存储方案都有其适用场景,因此选择哪一种方案应根据具体需求和资源情况综合考虑。对于需要快速写入和高度可扩展的审计日志系统,NoSQL数据库是一个不错的选择。

因此本文选择了 MongoDB 来记录日志。

选择MongoDB作为审计日志的存储方案,不仅因为它的高性能和可扩展性,还因为它支持灵活的文档数据模型,使得存储非结构化或半结构化的审计数据变得简单。

实现 AuditLogMongoService

在 C# 中使用 MongoDB 非常简单。

需要先添加 MongoDB.Driver 的 nuget 包

dotnet add MongoDB.Driver

直接上代码吧,

public class AuditLogMongoService : IAuditLogService {
  private readonly IMongoCollection<AuditLog> _auditLogs;

  public AuditLogMongoService(string connectionString, string databaseName) {
    var client = new MongoClient(connectionString);
    var database = client.GetDatabase(databaseName);
    _auditLogs = database.GetCollection<AuditLog>("audit_logs");
  }

  public async Task LogAsync(AuditLog auditLog) {
    await _auditLogs.InsertOneAsync(auditLog);
  }
}

准备连接字符串&注册服务

为了避免硬编码,将连接字符串放在配置文件(
appsettings.json
)里

"ConnectionStrings": {
  "Redis": "redis:6379",
  "MongoDB": "mongodb://username:password@path-to-mongo:27017"
}

注册服务

builder.Services.AddSingleton<IAuditLogService>(sp => new AuditLogMongoService(builder.Configuration.GetConnectionString("MongoDB"), "db_name"));

搞定~

部署 MongoDB

附上 MongoDB 的部署方法吧,我这里使用 docker ,很方便

version: '3.1'

services:

  mongo:
    image: mongo:4.4.6
    restart: always
    volumes:
      - ./data:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: username
      MONGO_INITDB_ROOT_PASSWORD: password
    ports:
      - 27017:27017

  mongo-express:
    image: mongo-express
    restart: always
    environment:
      ME_CONFIG_MONGODB_ADMINUSERNAME: username
      ME_CONFIG_MONGODB_ADMINPASSWORD: password
      ME_CONFIG_MONGODB_URL: mongodb://username:password@mongo:27017/
    ports:
      - 8081:8081

使用 docker-compose 来编排,映射了 27017 和 8081 端口

可以使用 8081 端口访问 mongo-express 网页服务

如何查看日志

  • 使用 MongoDB Compass 这个软件来查看数据
  • 使用 mongo-express 服务可以在网页上查看数据

小结

虽然是比较简单的功能,不过使用 AOP 来实现用起来感觉还是蛮爽的,不得不说 AspNetCore 的功能确实丰富~