using CloudBuilder.Core.DatabaseAccessor.Entity; using CloudBuilder.Core.DependencyInjection.EFCore; using CloudBuilder.Core.Policy; using CloudBuilder.Core.Service; using DocumentFormat.OpenXml.Math; using DocumentFormat.OpenXml.Vml.Office; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.JsonWebTokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CloudBuilder.Core.DatabaseAccessor { public class MasterDbContext : DbContext { public const string MASTER_DB_CONTEXT = "MasterDbContext"; public IEnumerable> _entityManagers; private readonly IServiceProvider _serviceProvider; private readonly IConfiguration configuration; private readonly IHttpContextAccessor httpContextAccessor; private FileLog fileLog; public MasterDbContext(DbContextOptions options, IEnumerable> entityManagers, IServiceProvider serviceProvider, IConfiguration configuration, IHttpContextAccessor httpContextAccessor ) : base(options) { _entityManagers = entityManagers; _serviceProvider = serviceProvider; this.configuration = configuration; this.httpContextAccessor = httpContextAccessor; fileLog = new FileLog(configuration); } public override int SaveChanges(bool acceptAllChangesOnSuccess) { try { SavingChangesEvent(); return base.SaveChanges(acceptAllChangesOnSuccess); } catch (Exception ex) { //自动检验数据是否过时 //ALTER TABLE table ADD row_version rowversion; //[Column("row_version")] //[Timestamp] //public byte[] RowVersion { get; set; } if (ex.Message.Contains("data may have been modified or deleted since entities were loaded.")) { throw new ValidatedException(CoreMessagePolicy.GetInstance().ComposeMessage(CoreMessagePolicy.THE_DATA_HAS_BEEN_MODIFIED)); } throw new ValidatedException(ex.InnerException?.Message ?? ex.Message); } } public void SavingChangesEvent() { var entities = this.ChangeTracker.Entries() .Where(u => (u.State == EntityState.Modified || u.State == EntityState.Deleted || u.State == EntityState.Added)).ToList(); if (entities == null || entities.Count < 1) return; string uid = this.httpContextAccessor.HttpContext?.User?.Identity?.Name; DateTime now = DateTime.Now; if (string.IsNullOrEmpty(uid)) { uid = GetUserIdFromToken(httpContextAccessor.HttpContext!); } foreach (var entity in entities) { if (!(entity.State == EntityState.Added || entity.State == EntityState.Modified)) continue; // 获取所有实体有效属性,排除 [NotMapper] 属性 var props = entity.OriginalValues.Properties; // 获取实体当前(现在)的值 var currentValues = entity.CurrentValues; // 遍历所有属性 foreach (var prop in props) { // 获取属性名 var propName = prop.Name; if (propName == IEntityAudited.CREATED_BY && entity.State == EntityState.Added) { currentValues[propName] = uid; continue; } if (propName == IEntityAudited.CREATED_DATETIME && entity.State == EntityState.Added) { currentValues[propName] = DateTime.Now; continue; } if (propName == IEntityAudited.UPDATED_BY) { currentValues[propName] = uid; continue; } if (propName == IEntityAudited.UPDATED_DATETIME) { currentValues[propName] = DateTime.Now; continue; } } } foreach (var entity in entities) { var entityName = entity.Metadata.GetTableName() ?? entity.Entity.GetType().Name; var state = entity.State; // ====================================== // 1. 获取所有主键(支持复合主键) // ====================================== var pkProps = entity.Properties .Where(p => p.Metadata.IsPrimaryKey()) .ToList(); // 拼接 WHERE 条件:Id=1 AND TenantId=2 var whereClauses = pkProps .Select(p => $"{p.Metadata.GetColumnName() ?? p.Metadata.Name} = '{p.CurrentValue?.ToString()?.Replace("'", "''")}'"); string whereSql = string.Join(" AND ", whereClauses); string sqlLog = ""; // ====================================== // 修改 UPDATE // ====================================== if (state == EntityState.Modified) { var setClauses = entity.Properties .Where(p => p.IsModified) // 只记录真正修改的字段 .Select(p => $"{p.Metadata.GetColumnName() ?? p.Metadata.Name} = '{p.CurrentValue?.ToString()?.Replace("'", "''")}'"); string setSql = string.Join(", ", setClauses); sqlLog = $"[{uid}][{now.ToString("yyyy-MM-dd HH:mm:ss")}]UPDATE {entityName} SET {setSql} WHERE {whereSql}"; } // ====================================== // 新增 INSERT // ====================================== else if (state == EntityState.Added) { var data = entity.Properties.ToDictionary(p => p.Metadata.GetColumnName() ?? p.Metadata.Name, p => p.CurrentValue); var columns = string.Join(", ", data.Keys); var values = string.Join(", ", data.Values.Select(v => $"'{v?.ToString()?.Replace("'", "''")}'")); sqlLog = $"[{uid}][{now.ToString("yyyy-MM-dd HH:mm:ss")}]INSERT INTO {entityName} ({columns}) VALUES ({values})"; } // ====================================== // 删除 DELETE // ====================================== else if (state == EntityState.Deleted) { sqlLog = $"[{uid}][{now.ToString("yyyy-MM-dd HH:mm:ss")}]DELETE FROM {entityName} WHERE {whereSql}"; } fileLog.WriteToFile(sqlLog); } } protected override void OnModelCreating(ModelBuilder builder) { foreach (IMasterRegistEntityType entityManager in _entityManagers) { entityManager.RegistEntity(builder); } base.OnModelCreating(builder); } public string GetJwtBearerToken(HttpContext httpContext, string headerKey = "Authorization", string tokenPrefix = "Bearer ") { if (httpContext == null) return null; // 判断请求报文头中是否有 "Authorization" 报文头 var bearerToken = httpContext.Request.Headers[headerKey].ToString(); if (string.IsNullOrWhiteSpace(bearerToken)) return default; var prefixLenght = tokenPrefix.Length; return bearerToken.StartsWith(tokenPrefix, true, null) && bearerToken.Length > prefixLenght ? bearerToken[prefixLenght..] : default; } public string GetUserIdFromToken(HttpContext httpContext) { string token = GetJwtBearerToken(httpContextAccessor.HttpContext!); if (!string.IsNullOrEmpty(token)) { try { JsonWebToken json = new JsonWebTokenHandler().ReadJsonWebToken(token); return json.GetClaim(ClaimPolicy.CLAINM_USERID).Value; } catch (Exception) { return string.Empty; } } return string.Empty; } } }