From 637bff638903af269e199434df720290dc357c12 Mon Sep 17 00:00:00 2001 From: sunpengfei <i@angelzzz.com> Date: 星期五, 05 九月 2025 11:23:46 +0800 Subject: [PATCH] feat:开发 --- FlexJobApi.Core/Utils/DbUtils/DbUtils.cs | 518 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 files changed, 461 insertions(+), 57 deletions(-) diff --git a/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs b/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs index e4270af..e8e12e3 100644 --- a/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs +++ b/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs @@ -1,10 +1,14 @@ -锘縰sing Furion; +锘縰sing Consul.Filtering; +using Furion; using Furion.DatabaseAccessor; using Furion.DistributedIDGenerator; +using Furion.FriendlyException; +using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.Extensions.Configuration; using Newtonsoft.Json; using System; @@ -12,12 +16,387 @@ using System.Linq; using System.Linq.Expressions; using System.Text; +using System.Threading; using System.Threading.Tasks; +using System.Timers; namespace FlexJobApi.Core { + /// <summary> + /// 鏁版嵁搴撳伐鍏� + /// </summary> public static class DbUtils { + /// <summary> + /// 鑾峰彇鏍戝舰鏁版嵁璺緞 + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <param name="parentId"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<string> GetTreeDataPath<TEntity>( + Guid? parentId, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, ITreeData<TEntity>, new() + { + var rep = Db.GetRepository<TEntity>(); + if (parentId.HasValue) + { + var parent = await rep.AsQueryable().AsNoTracking() + .Where(it => it.Id == parentId) + .Select(it => new + { + it.Path, + it.Code + }) + .FirstOrDefaultAsync(cancellationToken); + var summary = typeof(TEntity).GetSummary(); + if (parent == null) throw Oops.Oh(EnumErrorCodeType.s404, $"涓婄骇{summary}"); + return $"{parent.Path}{parent.Code}/"; + } + else + { + return "/"; + } + } + + /// <summary> + /// 鏇存柊鏍戝舰鏁版嵁涓嬬骇璺緞 + /// </summary> + /// <param name="oldPath"></param> + /// <param name="newPath"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task UpdateTreeDataChildrenPath<TEntity>( + string oldPath, + string newPath, + CancellationToken cancellationToken) + where TEntity : CommonEntity, ITreeData<TEntity>, new() + { + var rep = Db.GetRepository<TEntity>(); + var models = await rep.AsQueryable() + .Where(it => it.Path.StartsWith(oldPath)) + .ToListAsync(cancellationToken); + if (models.IsNotNull()) + { + foreach (var model in models) + { + model.Path = model.Path.Replace(oldPath, newPath); + } + await rep.UpdateAsync(models); + } + } + + /// <summary> + /// 鏌ヨ閫夋嫨鍣ㄦ暟鎹� + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <typeparam name="TValue"></typeparam> + /// <typeparam name="TData"></typeparam> + /// <param name="request"></param> + /// <param name="getValue"></param> + /// <param name="getLabel"></param> + /// <param name="query"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<List<SelectOption<TValue, TData>>> GetSelect<TEntity, TValue, TData>( + this SelectQuery<TValue, TData> request, + Func<TData, TValue> getValue, + Func<TData, string> getLabel, + Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, new() + { + var rep = Db.GetRepository<TEntity>(); + var q = rep.AsQueryable().AsNoTracking(); + if (query != null) q = query(q); + else q = q.OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime); + var models = await q + .ProjectToType<TData>() + .ToListAsync(cancellationToken); + var options = new List<SelectOption<TValue, TData>>(); + foreach (var model in models) + { + var option = new SelectOption<TValue, TData>(); + option.Data = model; + option.Value = getValue(model); + option.Label = getLabel(model); + options.Add(option); + } + return options; + } + + /// <summary> + /// 鏌ヨ鍒嗛〉鍒楄〃鏁版嵁 + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <typeparam name="TItem"></typeparam> + /// <param name="page"></param> + /// <param name="query"></param> + /// <param name="selector"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<PagedListQueryResult<TItem>> GetPagedListAsync<TEntity, TItem>( + this PagedListQueryPageModel page, + Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null, + Expression<Func<TEntity, TItem>> selector = null, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, new() + where TItem : class, new() + { + var rep = Db.GetRepository<TEntity>(); + var q = rep.AsQueryable().AsNoTracking(); + if (query != null) q = query(q); + else q = q.OrderBy(it => it.Sort).ThenBy(it => it.CreatedTime); + if (page.OrderInput.IsNotNull()) + q = q.CustomOrderBy(page.OrderInput); + var s = selector == null + ? q.ProjectToType<TItem>() + : q.Select(selector); + var pagedList = await s.ToPagedListAsync(page.Page, page.Rows, cancellationToken); + var result = new PagedListQueryResult<TItem>(); + result.PageModel = page.Adapt<PagedListQueryResultPageModel>(); + result.PageModel.TotalCount = pagedList.TotalCount; + result.PageModel.TotalPage = pagedList.TotalPages; + result.Data = pagedList.Items.ToList(); + return result; + } + /// <summary> + /// 鏌ヨ鍒嗛〉鍒楄〃鏁版嵁 + /// </summary> + /// <typeparam name="TResult"></typeparam> + /// <typeparam name="TItem"></typeparam> + /// <param name="page"></param> + /// <param name="q"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<TResult> GetPagedListAsync<TResult, TItem>( + this PagedListQueryPageModel page, + IQueryable<TItem> q, + CancellationToken cancellationToken = default) + where TItem : class, new() + where TResult : PagedListQueryResult<TItem>, new() + { + if (page.OrderInput.IsNotNull()) + q = q.CustomOrderBy(page.OrderInput); + var pagedList = await q.ToPagedListAsync(page.Page, page.Rows, cancellationToken); + var result = new TResult(); + result.PageModel = page.Adapt<PagedListQueryResultPageModel>(); + result.PageModel.TotalCount = pagedList.TotalCount; + result.PageModel.TotalPage = pagedList.TotalPages; + result.Data = pagedList.Items.ToList(); + return result; + } + + /// <summary> + /// 鏌ヨ鍒嗛〉鍒楄〃鏁版嵁 + /// </summary> + /// <typeparam name="TItem"></typeparam> + /// <param name="page"></param> + /// <param name="q"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static Task<PagedListQueryResult<TItem>> GetPagedListAsync<TItem>( + this PagedListQueryPageModel page, + IQueryable<TItem> q, + CancellationToken cancellationToken = default) + where TItem : class, new() + { + return GetPagedListAsync<PagedListQueryResult<TItem>, TItem>(page, q, cancellationToken); + } + + public static IOrderedQueryable<T> CustomOrderBy<T>(this IQueryable<T> q, List<PagedListQueryPageModelOrderInput> orders) + { + ParameterExpression parameter = Expression.Parameter(typeof(T), "p"); + PagedListQueryPageModelOrderInput orderInput = orders[0]; + IOrderedQueryable<T> orderedQueryable = (orderInput.Order == EnumPagedListOrder.Asc) + ? OrderBy(q, orderInput.Property, parameter) + : OrderByDescending(q, orderInput.Property, parameter); + for (int i = 1; i < orders.Count; i++) + { + PagedListQueryPageModelOrderInput orderInput2 = orders[i]; + orderedQueryable = (orderInput2.Order == EnumPagedListOrder.Asc) + ? ThenBy(orderedQueryable, orderInput2.Property, parameter) + : ThenByDescending(orderedQueryable, orderInput2.Property, parameter); + } + + return orderedQueryable; + } + + private static IOrderedQueryable<T> Ordering<T>(IQueryable<T> source, ParameterExpression parameter, string propertyName, string methodName) + { + Type typeFromHandle = typeof(T); + MemberExpression memberExpression = Expression.PropertyOrField(parameter, propertyName); + LambdaExpression expression = Expression.Lambda(memberExpression, parameter); + MethodCallExpression expression2 = Expression.Call(typeof(Queryable), methodName, [typeFromHandle, memberExpression.Type], source.Expression, Expression.Quote(expression)); + return (IOrderedQueryable<T>)source.Provider.CreateQuery<T>(expression2); + } + + public static IOrderedQueryable<T> OrderBy<T>(IQueryable<T> source, string propertyName, ParameterExpression parameter) + { + return Ordering(source, parameter, propertyName, "OrderBy"); + } + + public static IOrderedQueryable<T> OrderByDescending<T>(IQueryable<T> source, string propertyName, ParameterExpression parameter) + { + return Ordering(source, parameter, propertyName, "OrderByDescending"); + } + + public static IOrderedQueryable<T> ThenBy<T>(IOrderedQueryable<T> source, string propertyName, ParameterExpression parameter) + { + return Ordering(source, parameter, propertyName, "ThenBy"); + } + + public static IOrderedQueryable<T> ThenByDescending<T>(IOrderedQueryable<T> source, string propertyName, ParameterExpression parameter) + { + return Ordering(source, parameter, propertyName, "ThenByDescending"); + } + + public static async Task<TResult> GetDetail<TEntity, TResult>( + this Guid id, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, new() + { + var rep = Db.GetRepository<TEntity>(); + return await rep.AsQueryable().AsNoTracking() + .Where(it => it.Id == id) + .GetDetail<TEntity, TResult>(cancellationToken); + } + + public static async Task<TResult> GetDetail<TEntity, TResult>( + this IQueryable<TEntity> q, + CancellationToken cancellationToken = default) + { + var model = await q + .ProjectToType<TResult>() + .FirstOrDefaultAsync(cancellationToken); + if (model == null) + { + var summary = await typeof(TEntity).GetSummary(); + throw Oops.Oh(EnumErrorCodeType.s404, $"{summary ?? "淇℃伅"}"); + } + return model; + } + + /// <summary> + /// 璁剧疆鏄惁绂佺敤 + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <param name="request"></param> + /// <param name="query"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<int> SetIsDisabled<TEntity>( + this SetIsDisabledCommand request, + Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, IIsDisabled, new() + { + var rep = Db.GetRepository<TEntity>(); + var q = rep.AsQueryable(); + if (query != null) q = query(q); + var entities = await q + .Where(it => request.Ids.Contains(it.Id) && it.IsDisabled != request.IsDisabled) + .ToListAsync(); + foreach (var entity in entities) + { + entity.IsDisabled = request.IsDisabled; + } + return entities.Count; + } + + /// <summary> + /// 鍒犻櫎鏁版嵁 + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <param name="request"></param> + /// <param name="query"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<int> DeleteData<TEntity>( + this DeleteDataCommand request, Func<IQueryable<TEntity>, + IQueryable<TEntity>> query = null, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, new() + { + var rep = Db.GetRepository<TEntity>(); + var q = rep.AsQueryable(); + if (query != null) q = query(q); + var entities = await q + .Where(it => request.Ids.Contains(it.Id)) + .ToListAsync(cancellationToken); + return entities.Any() + ? await rep.DeleteNowAsync(entities, cancellationToken) + : 0; + } + + public static async Task<Guid> UpdateData<TEntity, TRequest>( + this IQueryable<TEntity> q, + TRequest request, + Action<TEntity> update = null, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, new() + { + var rep = Db.GetRepository<TEntity>(); + var entity = await q.FirstOrDefaultAsync(); + if (entity == null) + { + var summary = await typeof(TEntity).GetSummary(); + throw Oops.Oh(EnumErrorCodeType.s404, $"{summary ?? "淇℃伅"}"); + } + + if (update != null) update(entity); + else request.Adapt(entity); + await rep.UpdateAsync(entity); + return entity.Id; + } + + /// <summary> + /// 淇濆瓨鏁版嵁 + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <typeparam name="TRequest"></typeparam> + /// <param name="request"></param> + /// <param name="query"></param> + /// <param name="checkExist"></param> + /// <param name="update"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<TEntity> SaveData<TEntity, TRequest>( + this TRequest request, + Func<IQueryable<TEntity>, IQueryable<TEntity>> query = null, + Expression<Func<TEntity, bool>> checkExist = null, + Action<TEntity> update = null, + CancellationToken cancellationToken = default) + where TEntity : CommonEntity, new() + where TRequest : SaveDataCommand, new() + { + var xmlDoc = await XmlDocUtils.GetXmlDocAsync(); + var summary = await typeof(TEntity).GetSummary(xmlDoc); + var rep = Db.GetRepository<TEntity>(); + if (checkExist != null && await rep.AsQueryable().AsNoTracking().AnyAsync(checkExist)) + throw Oops.Oh(EnumErrorCodeType.s405, $"{summary ?? "淇℃伅"}"); + if (request.Id.HasValue) + { + var q = rep.AsQueryable(); + if (query != null) q = query(q); + var entity = await q.FirstOrDefaultAsync(it => it.Id == request.Id, cancellationToken); + if (entity == null) throw Oops.Oh(EnumErrorCodeType.s404, $"{summary ?? "淇℃伅"}"); + if (update != null) update(entity); + else request.Adapt(entity); + await rep.UpdateAsync(entity); + return entity; + } + else + { + var entity = new TEntity(); + if (update != null) update(entity); + else request.Adapt(entity); + await rep.InsertAsync(entity); + return entity; + } + } + /// <summary> /// 鐢熸垚瀹炰綋 /// </summary> @@ -26,35 +405,41 @@ /// <returns></returns> public static async Task BuildEntity(ModelBuilder modelBuilder, Type dbContextLocator = null) { - var xmlDoc = await XmlDocUtils.GetXmlDocAsync(); - foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + if (App.GetService<IMigrator>() != null) { - Console.WriteLine($"姝e湪鐢熸垚琛細{entityType.Name}"); - // 鑾峰彇瀹炰綋绫荤殑XML娉ㄩ噴锛屽苟璁剧疆涓鸿〃娉ㄩ噴 - var entityBuilder = modelBuilder.Entity(entityType.ClrType); - string typeComment = (await entityType.ClrType.GetXmlDocMemberAsync(xmlDoc))?.Summary; - if (!string.IsNullOrEmpty(typeComment)) + var xmlDoc = await XmlDocUtils.GetXmlDocAsync(); + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { - Console.WriteLine($"姝e湪鐢熸垚琛ㄦ敞閲婏細{entityType.Name}-{typeComment}"); - entityBuilder.ToTable(it => it.HasComment(typeComment)); - } - // 鑾峰彇瀹炰綋灞炴�х殑XML娉ㄩ噴锛屽苟璁剧疆涓哄垪娉ㄩ噴 - var properties = entityType.ClrType.GetProperties() - .Where(p => - p.CanRead - && p.CanWrite - && !typeof(System.Collections.ICollection).IsAssignableFrom(p.PropertyType) - && (p.PropertyType.IsClass ? p.PropertyType.FullName.Contains("System.") : true)); - foreach (var property in properties) - { - string propComment = (await property.GetXmlDocMemberAsync(xmlDoc))?.Summary; - if (!string.IsNullOrEmpty(propComment)) + Console.WriteLine($"姝e湪鐢熸垚琛細{entityType.Name}"); + // 鑾峰彇瀹炰綋绫荤殑XML娉ㄩ噴锛屽苟璁剧疆涓鸿〃娉ㄩ噴 + var entityBuilder = modelBuilder.Entity(entityType.ClrType); + string typeComment = (await entityType.ClrType.GetXmlDocMemberAsync(xmlDoc))?.Summary; + if (!string.IsNullOrEmpty(typeComment)) { - Console.WriteLine($"姝e湪鐢熸垚灞炴�ф敞閲婏細{property.Name}-{propComment}"); - entityBuilder.Property(property.Name).HasComment(propComment); + Console.WriteLine($"姝e湪鐢熸垚琛ㄦ敞閲婏細{entityType.Name}-{typeComment}"); + entityBuilder.ToTable(it => it.HasComment(typeComment)); + } + // 鑾峰彇瀹炰綋灞炴�х殑XML娉ㄩ噴锛屽苟璁剧疆涓哄垪娉ㄩ噴 + var properties = entityType.ClrType.GetProperties() + .Where(p => + p.CanRead + && p.CanWrite + && !typeof(System.Collections.ICollection).IsAssignableFrom(p.PropertyType) + && (p.PropertyType.IsClass ? p.PropertyType.FullName.Contains("System.") : true)); + foreach (var property in properties) + { + string propComment = (await property.GetXmlDocMemberAsync(xmlDoc))?.Summary; + if (!string.IsNullOrEmpty(propComment)) + { + Console.WriteLine($"姝e湪鐢熸垚灞炴�ф敞閲婏細{property.Name}-{propComment}"); + entityBuilder.Property(property.Name).HasComment(propComment); + } } } + } + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) + { if (typeof(CommonEntity).IsAssignableFrom(entityType.ClrType)) { // 鏋勫缓绛涢�夋潯浠讹細IsDeleted == false @@ -68,10 +453,13 @@ modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda); } } - - Console.WriteLine("鏁版嵁搴撻摼鎺ュ湴鍧�锛�" + App.Configuration.GetConnectionString("FlexJobApi")); } + /// <summary> + /// 鏁版嵁鍙樻洿浜嬩欢 + /// </summary> + /// <param name="eventData"></param> + /// <param name="result"></param> public static void SavingChangesEvent(DbContextEventData eventData, InterceptionResult<int> result) { // 鑾峰彇褰撳墠浜嬩欢瀵瑰簲涓婁笅鏂� @@ -93,6 +481,7 @@ // 閫氳繃璇锋眰涓幏鍙栧綋鍓嶆搷浣滀汉 var logier = JwtUtils.GetCurrentLogier(); + var traceId = App.GetTraceId(); // 鑾峰彇鎵�鏈夊凡鏇存敼鐨勫疄浣� foreach (var entity in entities) @@ -118,17 +507,24 @@ } // 璧嬪�肩敤鎴蜂俊鎭疘d - prop = entity.Property(nameof(CommonEntity.CreatedUserInfoId)); + prop = entity.Property(nameof(CommonEntity.CreatedUserId)); if (prop != null && prop.CurrentValue == null) { - prop.CurrentValue = logier?.UserInfoId; + prop.CurrentValue = logier?.Id; + } + + // 璧嬪�间紒涓欼d + prop = entity.Property(nameof(CommonEntity.CreatedEnterpriseId)); + if (prop != null && prop.CurrentValue == null) + { + prop.CurrentValue = logier?.EnterpriseId; } // 璧嬪�艰窡韪狪d prop = entity.Property(nameof(CommonEntity.TraceId)); - if (prop != null) + if (prop != null && prop.CurrentValue == null && traceId.IsNotNull()) { - prop.CurrentValue = App.GetTraceId(); + prop.CurrentValue = traceId; } } else @@ -141,17 +537,17 @@ } // 璧嬪�肩敤鎴蜂俊鎭疘d - prop = entity.Property(nameof(CommonEntity.UpdatedUserInfoId)); + prop = entity.Property(nameof(CommonEntity.UpdatedUserId)); if (prop != null) { - prop.CurrentValue = logier?.UserInfoId; + prop.CurrentValue = logier?.Id; } // 璧嬪�艰窡韪狪d prop = entity.Property(nameof(CommonEntity.TraceId)); - if (prop != null) + if (prop != null && traceId.IsNotNull()) { - prop.CurrentValue = App.GetTraceId(); + prop.CurrentValue = traceId; } // 杞垹闄� @@ -172,36 +568,44 @@ continue; } - Db.GetRepository<DbAuditLog, LogDbContextLocator>().InsertNow(new DbAuditLog + var log = new DbAuditLog { Id = IDGen.NextID(), TableName = entityType.Name, PrimaryKey = (Guid)entity.Property("Id").CurrentValue, - NewValues = entity.State == EntityState.Added || entity.State == EntityState.Modified - ? GetPropertyValuesAsJson(entity) - : null, - OldValues = entity.State == EntityState.Deleted || entity.State == EntityState.Modified - ? GetPropertyValuesAsJson(entity, entity.State == EntityState.Modified) - : null, - Operate = entity.State == EntityState.Added - ? EnumDbAuditOperate.Added - : entity.State == EntityState.Modified - ? EnumDbAuditOperate.Modified - : EnumDbAuditOperate.Deleted, TraceId = App.GetTraceId(), CreatedTime = DateTime.Now, - CreatedUserInfoId = logier?.UserInfoId, - }); + CreatedUserId = logier?.Id, + CreatedEnterpriseId = logier?.EnterpriseId + }; + log.Operate = + entity.State == EntityState.Added + ? EnumDbAuditOperate.Added + : entity.State == EntityState.Modified + ? EnumDbAuditOperate.Modified + : EnumDbAuditOperate.Deleted; + if (entity.Property(nameof(CommonEntity.IsDeleted)).CurrentValue is bool isDeleted && isDeleted == true) + { + log.Operate = EnumDbAuditOperate.Deleted; + } + log.NewValues = + log.Operate == EnumDbAuditOperate.Deleted + ? null + : JsonConvert.SerializeObject(entity.Properties + .Where(p => log.Operate == EnumDbAuditOperate.Modified + ? p.IsModified + : true) + .ToDictionary(p => p.Metadata.Name, p => p.CurrentValue)); + log.OldValues = + log.Operate == EnumDbAuditOperate.Added + ? null + : JsonConvert.SerializeObject(entity.Properties + .Where(p => log.Operate == EnumDbAuditOperate.Modified + ? p.IsModified + : true) + .ToDictionary(p => p.Metadata.Name, p => p.OriginalValue)); + Db.GetRepository<DbAuditLog, LogDbContextLocator>().InsertNow(log); } - } - - private static string GetPropertyValuesAsJson(EntityEntry entry, bool getOldValues = false) - { - var properties = entry.Properties - .Where(p => getOldValues ? p.IsModified : true) - .ToDictionary(p => p.Metadata.Name, p => getOldValues ? p.OriginalValue : p.CurrentValue); - - return JsonConvert.SerializeObject(properties); } } } -- Gitblit v1.9.1