corps/src/MilitaryTrainingManagement/Services/Implementations/AuditService.cs
2026-01-14 23:36:09 +08:00

421 lines
15 KiB
C#

using System.Reflection;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using MilitaryTrainingManagement.Data;
using MilitaryTrainingManagement.Models.Entities;
using MilitaryTrainingManagement.Services.Interfaces;
namespace MilitaryTrainingManagement.Services.Implementations;
/// <summary>
/// 审计服务实现
/// </summary>
public class AuditService : IAuditService
{
private readonly ApplicationDbContext _context;
private readonly ILogger<AuditService> _logger;
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public AuditService(ApplicationDbContext context, ILogger<AuditService> logger)
{
_context = context;
_logger = logger;
}
public async Task LogAsync(AuditLogEntry entry)
{
var log = new AuditLog
{
EntityType = entry.EntityType,
EntityId = entry.EntityId,
Action = entry.Action,
Description = entry.Description,
OldValues = entry.OldValues,
NewValues = entry.NewValues,
ChangedFields = entry.ChangedFields,
UserId = entry.UserId,
OrganizationalUnitId = entry.OrganizationalUnitId,
IpAddress = entry.IpAddress,
UserAgent = entry.UserAgent,
RequestPath = entry.RequestPath,
IsSuccess = entry.IsSuccess,
ErrorMessage = entry.ErrorMessage,
Timestamp = DateTime.UtcNow
};
_context.AuditLogs.Add(log);
await _context.SaveChangesAsync();
_logger.LogInformation("审计日志已记录: {EntityType} {EntityId} {Action}",
entry.EntityType, entry.EntityId, entry.Action);
}
public async Task LogAsync(string entityType, int entityId, string action, string? oldValues, string? newValues, int? userId, string? ipAddress)
{
await LogAsync(new AuditLogEntry
{
EntityType = entityType,
EntityId = entityId,
Action = action,
OldValues = oldValues,
NewValues = newValues,
UserId = userId,
IpAddress = ipAddress
});
}
public async Task LogCreateAsync<T>(T entity, int? userId, int? organizationalUnitId, string? ipAddress = null, string? userAgent = null, string? requestPath = null) where T : class
{
var entityType = typeof(T).Name;
var entityId = GetEntityId(entity);
var newValues = SerializeEntity(entity);
await LogAsync(new AuditLogEntry
{
EntityType = entityType,
EntityId = entityId,
Action = AuditActions.Create,
Description = $"创建{GetEntityDisplayName(entityType)}记录",
NewValues = newValues,
UserId = userId,
OrganizationalUnitId = organizationalUnitId,
IpAddress = ipAddress,
UserAgent = userAgent,
RequestPath = requestPath
});
}
public async Task LogUpdateAsync<T>(T originalEntity, T updatedEntity, int? userId, int? organizationalUnitId, string? ipAddress = null, string? userAgent = null, string? requestPath = null) where T : class
{
var entityType = typeof(T).Name;
var entityId = GetEntityId(updatedEntity);
var oldValues = SerializeEntity(originalEntity);
var newValues = SerializeEntity(updatedEntity);
var changedFields = GetChangedFields(originalEntity, updatedEntity);
await LogAsync(new AuditLogEntry
{
EntityType = entityType,
EntityId = entityId,
Action = AuditActions.Update,
Description = $"更新{GetEntityDisplayName(entityType)}记录",
OldValues = oldValues,
NewValues = newValues,
ChangedFields = JsonSerializer.Serialize(changedFields, JsonOptions),
UserId = userId,
OrganizationalUnitId = organizationalUnitId,
IpAddress = ipAddress,
UserAgent = userAgent,
RequestPath = requestPath
});
}
public async Task LogDeleteAsync<T>(T entity, int? userId, int? organizationalUnitId, string? ipAddress = null, string? userAgent = null, string? requestPath = null) where T : class
{
var entityType = typeof(T).Name;
var entityId = GetEntityId(entity);
var oldValues = SerializeEntity(entity);
await LogAsync(new AuditLogEntry
{
EntityType = entityType,
EntityId = entityId,
Action = AuditActions.Delete,
Description = $"删除{GetEntityDisplayName(entityType)}记录",
OldValues = oldValues,
UserId = userId,
OrganizationalUnitId = organizationalUnitId,
IpAddress = ipAddress,
UserAgent = userAgent,
RequestPath = requestPath
});
}
public async Task LogApprovalAsync(string entityType, int entityId, string action, string? description, int? userId, int? organizationalUnitId, string? ipAddress = null)
{
await LogAsync(new AuditLogEntry
{
EntityType = entityType,
EntityId = entityId,
Action = action,
Description = description ?? $"{action}{GetEntityDisplayName(entityType)}记录",
UserId = userId,
OrganizationalUnitId = organizationalUnitId,
IpAddress = ipAddress
});
}
public async Task LogFailureAsync(string entityType, int entityId, string action, string errorMessage, int? userId, int? organizationalUnitId, string? ipAddress = null)
{
await LogAsync(new AuditLogEntry
{
EntityType = entityType,
EntityId = entityId,
Action = action,
Description = $"{action}操作失败",
UserId = userId,
OrganizationalUnitId = organizationalUnitId,
IpAddress = ipAddress,
IsSuccess = false,
ErrorMessage = errorMessage
});
}
public async Task<IEnumerable<AuditLog>> GetLogsAsync(AuditLogQueryParameters parameters)
{
var query = _context.AuditLogs
.Include(l => l.User)
.Include(l => l.OrganizationalUnit)
.AsQueryable();
if (!string.IsNullOrEmpty(parameters.EntityType))
query = query.Where(l => l.EntityType == parameters.EntityType);
if (parameters.EntityId.HasValue)
query = query.Where(l => l.EntityId == parameters.EntityId.Value);
if (!string.IsNullOrEmpty(parameters.Action))
query = query.Where(l => l.Action == parameters.Action);
if (parameters.UserId.HasValue)
query = query.Where(l => l.UserId == parameters.UserId.Value);
if (parameters.OrganizationalUnitId.HasValue)
query = query.Where(l => l.OrganizationalUnitId == parameters.OrganizationalUnitId.Value);
if (parameters.FromDate.HasValue)
query = query.Where(l => l.Timestamp >= parameters.FromDate.Value);
if (parameters.ToDate.HasValue)
query = query.Where(l => l.Timestamp <= parameters.ToDate.Value);
if (parameters.IsSuccess.HasValue)
query = query.Where(l => l.IsSuccess == parameters.IsSuccess.Value);
return await query
.OrderByDescending(l => l.Timestamp)
.Skip((parameters.PageNumber - 1) * parameters.PageSize)
.Take(parameters.PageSize)
.ToListAsync();
}
public async Task<int> GetLogCountAsync(AuditLogQueryParameters parameters)
{
var query = _context.AuditLogs.AsQueryable();
if (!string.IsNullOrEmpty(parameters.EntityType))
query = query.Where(l => l.EntityType == parameters.EntityType);
if (parameters.EntityId.HasValue)
query = query.Where(l => l.EntityId == parameters.EntityId.Value);
if (!string.IsNullOrEmpty(parameters.Action))
query = query.Where(l => l.Action == parameters.Action);
if (parameters.UserId.HasValue)
query = query.Where(l => l.UserId == parameters.UserId.Value);
if (parameters.OrganizationalUnitId.HasValue)
query = query.Where(l => l.OrganizationalUnitId == parameters.OrganizationalUnitId.Value);
if (parameters.FromDate.HasValue)
query = query.Where(l => l.Timestamp >= parameters.FromDate.Value);
if (parameters.ToDate.HasValue)
query = query.Where(l => l.Timestamp <= parameters.ToDate.Value);
if (parameters.IsSuccess.HasValue)
query = query.Where(l => l.IsSuccess == parameters.IsSuccess.Value);
return await query.CountAsync();
}
public async Task<AuditLog?> GetLogByIdAsync(int id)
{
return await _context.AuditLogs
.Include(l => l.User)
.Include(l => l.OrganizationalUnit)
.FirstOrDefaultAsync(l => l.Id == id);
}
public async Task<IEnumerable<AuditLog>> GetLogsAsync(string? entityType = null, int? entityId = null, DateTime? fromDate = null, DateTime? toDate = null)
{
return await GetLogsAsync(new AuditLogQueryParameters
{
EntityType = entityType,
EntityId = entityId,
FromDate = fromDate,
ToDate = toDate
});
}
public async Task<IEnumerable<AuditLog>> GetEntityHistoryAsync(string entityType, int entityId)
{
return await _context.AuditLogs
.Include(l => l.User)
.Include(l => l.OrganizationalUnit)
.Where(l => l.EntityType == entityType && l.EntityId == entityId)
.OrderByDescending(l => l.Timestamp)
.ToListAsync();
}
public async Task<IEnumerable<AuditLog>> GetUserActivityAsync(int userId, DateTime? fromDate = null, DateTime? toDate = null)
{
var query = _context.AuditLogs
.Include(l => l.OrganizationalUnit)
.Where(l => l.UserId == userId);
if (fromDate.HasValue)
query = query.Where(l => l.Timestamp >= fromDate.Value);
if (toDate.HasValue)
query = query.Where(l => l.Timestamp <= toDate.Value);
return await query.OrderByDescending(l => l.Timestamp).ToListAsync();
}
public async Task<IEnumerable<AuditLog>> GetOrganizationalUnitActivityAsync(int organizationalUnitId, DateTime? fromDate = null, DateTime? toDate = null)
{
var query = _context.AuditLogs
.Include(l => l.User)
.Where(l => l.OrganizationalUnitId == organizationalUnitId);
if (fromDate.HasValue)
query = query.Where(l => l.Timestamp >= fromDate.Value);
if (toDate.HasValue)
query = query.Where(l => l.Timestamp <= toDate.Value);
return await query.OrderByDescending(l => l.Timestamp).ToListAsync();
}
public async Task<AuditLogStatistics> GetStatisticsAsync(DateTime? fromDate = null, DateTime? toDate = null)
{
var query = _context.AuditLogs.AsQueryable();
if (fromDate.HasValue)
query = query.Where(l => l.Timestamp >= fromDate.Value);
if (toDate.HasValue)
query = query.Where(l => l.Timestamp <= toDate.Value);
var logs = await query.ToListAsync();
return new AuditLogStatistics
{
TotalLogs = logs.Count,
CreateOperations = logs.Count(l => l.Action == AuditActions.Create),
UpdateOperations = logs.Count(l => l.Action == AuditActions.Update),
DeleteOperations = logs.Count(l => l.Action == AuditActions.Delete),
ApprovalOperations = logs.Count(l => l.Action == AuditActions.Approve || l.Action == AuditActions.Reject),
FailedOperations = logs.Count(l => !l.IsSuccess),
OperationsByEntityType = logs.GroupBy(l => l.EntityType).ToDictionary(g => g.Key, g => g.Count()),
OperationsByAction = logs.GroupBy(l => l.Action).ToDictionary(g => g.Key, g => g.Count())
};
}
#region Helper Methods
private static int GetEntityId<T>(T entity) where T : class
{
var idProperty = typeof(T).GetProperty("Id");
if (idProperty != null)
{
var value = idProperty.GetValue(entity);
if (value is int intValue)
return intValue;
}
return 0;
}
private static string SerializeEntity<T>(T entity) where T : class
{
try
{
// 创建一个只包含简单属性的字典,避免循环引用
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => IsSimpleType(p.PropertyType))
.ToDictionary(p => p.Name, p => p.GetValue(entity));
return JsonSerializer.Serialize(properties, JsonOptions);
}
catch (Exception)
{
return "{}";
}
}
private static bool IsSimpleType(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
return underlyingType.IsPrimitive
|| underlyingType == typeof(string)
|| underlyingType == typeof(decimal)
|| underlyingType == typeof(DateTime)
|| underlyingType == typeof(DateTimeOffset)
|| underlyingType == typeof(Guid)
|| underlyingType.IsEnum;
}
private static List<string> GetChangedFields<T>(T original, T updated) where T : class
{
var changedFields = new List<string>();
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => IsSimpleType(p.PropertyType));
foreach (var property in properties)
{
var originalValue = property.GetValue(original);
var updatedValue = property.GetValue(updated);
if (!Equals(originalValue, updatedValue))
{
changedFields.Add(property.Name);
}
}
return changedFields;
}
private static string GetEntityDisplayName(string entityType)
{
return entityType switch
{
"MaterialAllocation" => "物资配额",
"AllocationDistribution" => "配额分配",
"Personnel" => "人员",
"OrganizationalUnit" => "组织单位",
"UserAccount" => "用户账户",
"ApprovalRequest" => "审批请求",
_ => entityType
};
}
#endregion
}
/// <summary>
/// 审计操作类型常量
/// </summary>
public static class AuditActions
{
public const string Create = "Create";
public const string Update = "Update";
public const string Delete = "Delete";
public const string Approve = "Approve";
public const string Reject = "Reject";
public const string Submit = "Submit";
public const string Login = "Login";
public const string Logout = "Logout";
public const string Transfer = "Transfer";
public const string Distribute = "Distribute";
public const string Report = "Report";
}