using System.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using MS.Microservice.Core.Auditing; namespace MS.Microservice.AspNetCore.Auditing; public sealed record AuditHttpIdentity(string Scope, string Actor); public sealed class AuditHttpOptions { /// Resolves identity from host authentication; returning null denies an audited action. public Func ResolveIdentity { get; set; } = null!; } public static class AuditOperationMvcExtensions { /// Creates a shared audit operation for tagged MVC actions without recording request bodies or results. public static IMvcBuilder AddAutomaticAuditOperations(this IMvcBuilder builder, Action configure) { ArgumentNullException.ThrowIfNull(configure); var options = new AuditHttpOptions(); configure(options); ArgumentNullException.ThrowIfNull(options.ResolveIdentity); builder.Services.AddSingleton(options); builder.Services.TryAddScoped(); builder.Services.TryAddSingleton(); builder.AddMvcOptions(mvc => mvc.Filters.AddService()); return builder; } } internal sealed class AuditOperationFilter(AuditHttpOptions options) : IAsyncActionFilter, IOrderedFilter { public int Order => int.MinValue + 100; public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var marker = context.HttpContext.GetEndpoint()?.Metadata.GetMetadata(); if (marker is null) { await next(); return; } var identity = options.ResolveIdentity(context.HttpContext); if (identity is null) { context.Result = new ObjectResult(new ProblemDetails { Status = 403, Title = "缺少可信审计身份。" }) { StatusCode = 403 }; return; } using var scope = context.HttpContext.RequestServices.GetRequiredService().Begin(identity.Scope, identity.Actor, marker.Name, correlationId: Activity.Current?.TraceId.ToString() ?? context.HttpContext.TraceIdentifier); await next(); } }