From bc6813b74e9a390eae2181d460c647445b7cb25a Mon Sep 17 00:00:00 2001 From: sunpengfei <i@angelzzz.com> Date: 星期三, 06 八月 2025 11:22:18 +0800 Subject: [PATCH] feat:数据字典开发 --- FlexJobApi.Core/Utils/DbUtils/DbUtils.cs | 261 ++++++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 222 insertions(+), 39 deletions(-) diff --git a/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs b/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs index ffb3bae..e6a1389 100644 --- a/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs +++ b/FlexJobApi.Core/Utils/DbUtils/DbUtils.cs @@ -1,55 +1,226 @@ 锘縰sing 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.Extensions.Configuration; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace FlexJobApi.Core { + /// <summary> + /// 鏁版嵁搴撳伐鍏� + /// </summary> public static class DbUtils { /// <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>> ToPagedListAsync<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); + q = q.OrderBy(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="T"></typeparam> + /// <param name="q"></param> + /// <param name="orders"></param> + /// <returns></returns> + public static IQueryable<T> OrderBy<T>(this IQueryable<T> q, List<PagedListQueryPageModelOrderInput> orders) + { + if (orders.IsNull()) return q; + + var entityType = typeof(T); + int index = 0; + + var props = entityType.GetProperties(); + foreach (var order in orders) + { + if (string.IsNullOrEmpty(order.Property)) continue; + + // 鑾峰彇鎺掑簭瀛楁鐨勫睘鎬т俊鎭� + var propertyInfo = props.FirstOrDefault(it => it.Name.Equals(order.Property, StringComparison.OrdinalIgnoreCase)); + if (propertyInfo == null) throw Oops.Oh(EnumErrorCodeType.s404, $"璇ユ帓搴忓瓧娈祘order.Property}"); + + // 鍒涘缓琛ㄨ揪寮忔爲 + var parameter = Expression.Parameter(entityType, "x"); + var propertyAccess = Expression.Property(parameter, propertyInfo); + var lambda = Expression.Lambda(propertyAccess, parameter); + + string methodName; + if (index == 0) + { + // 棣栨鎺掑簭 + methodName = order.Order == EnumPagedListOrder.Asc + ? "OrderBy" + : "OrderByDescending"; + } + else + { + // 浜屾鍙婁互鍚庢帓搴� + methodName = order.Order == EnumPagedListOrder.Asc + ? "ThenBy" + : "ThenByDescending"; + } + + // 璋冪敤鐩稿簲鐨勬帓搴忔柟娉� + var resultExpression = Expression.Call( + typeof(Queryable), + methodName, + [entityType, propertyInfo.PropertyType], + q.Expression, + Expression.Quote(lambda) + ); + + q = q.Provider.CreateQuery<T>(resultExpression); + index++; + } + + return q; + } + + /// <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> SetIsDisable<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; + } + + /// <summary> + /// 淇濆瓨鏁版嵁 + /// </summary> + /// <typeparam name="TEntity"></typeparam> + /// <typeparam name="TRequest"></typeparam> + /// <param name="request"></param> + /// <param name="checkExist"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + public static async Task<Guid> SaveData<TEntity, TRequest>(this TRequest request, Func<IQueryable<TEntity>, TEntity, TRequest, bool> checkExist = 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 (request.Id.HasValue) + { + var entity = await rep.AsQueryable().FirstOrDefaultAsync(it => it.Id == request.Id, cancellationToken); + if (entity == null) throw Oops.Oh(EnumErrorCodeType.s404, $"璇summary ?? "淇℃伅"}"); + if (checkExist != null && checkExist(rep.AsQueryable().AsNoTracking(), entity, request)) throw Oops.Oh(EnumErrorCodeType.s405, $"璇summary ?? "淇℃伅"}"); + request.Adapt(entity); + await rep.UpdateAsync(entity); + return entity.Id; + } + else + { + var entity = new TEntity(); + if (checkExist != null && checkExist(rep.AsQueryable().AsNoTracking(), entity, request)) throw Oops.Oh(EnumErrorCodeType.s405, $"璇summary ?? "淇℃伅"}"); + request.Adapt(entity); + await rep.InsertAsync(entity); + return entity.Id; + } + } + + /// <summary> + /// 鐢熸垚瀹炰綋 /// </summary> /// <param name="modelBuilder"></param> /// <param name="dbContextLocator"></param> /// <returns></returns> - public static async Task BuildEntityComment(ModelBuilder modelBuilder, Type dbContextLocator = null) + public static async Task BuildEntity(ModelBuilder modelBuilder, Type dbContextLocator = null) { var xmlDoc = await XmlDocUtils.GetXmlDocAsync(); - var entityTypes = App.Assemblies - .Where(it => it.FullName.Contains("FlexJob")) - .SelectMany(it => it.GetTypes()) - .Where(it => - it.IsClass - && !it.IsAbstract - && typeof(IPrivateEntity).IsAssignableFrom(it) - && (dbContextLocator == null - ? it.BaseType == typeof(CommonEntity) - : it.BaseType.GenericTypeArguments.Any(it => it == dbContextLocator))) - .ToList(); - foreach (var entityType in entityTypes) + foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { Console.WriteLine($"姝e湪鐢熸垚琛細{entityType.Name}"); // 鑾峰彇瀹炰綋绫荤殑XML娉ㄩ噴锛屽苟璁剧疆涓鸿〃娉ㄩ噴 - var entityBuilder = modelBuilder.Entity(entityType); - string typeComment = (await entityType.GetXmlDocMemberAsync(xmlDoc))?.Summary; + var entityBuilder = modelBuilder.Entity(entityType.ClrType); + string typeComment = (await entityType.ClrType.GetXmlDocMemberAsync(xmlDoc))?.Summary; if (!string.IsNullOrEmpty(typeComment)) { Console.WriteLine($"姝e湪鐢熸垚琛ㄦ敞閲婏細{entityType.Name}-{typeComment}"); entityBuilder.ToTable(it => it.HasComment(typeComment)); } // 鑾峰彇瀹炰綋灞炴�х殑XML娉ㄩ噴锛屽苟璁剧疆涓哄垪娉ㄩ噴 - var properties = entityType.GetProperties() + var properties = entityType.ClrType.GetProperties() .Where(p => p.CanRead && p.CanWrite @@ -64,9 +235,29 @@ entityBuilder.Property(property.Name).HasComment(propComment); } } + + if (typeof(CommonEntity).IsAssignableFrom(entityType.ClrType)) + { + // 鏋勫缓绛涢�夋潯浠讹細IsDeleted == false + var parameter = Expression.Parameter(entityType.ClrType, "e"); + var property = Expression.Property(parameter, "IsDeleted"); + var constant = Expression.Constant(false); + var equal = Expression.Equal(property, constant); + var lambda = Expression.Lambda(equal, parameter); + + // 娣诲姞鍏ㄥ眬绛涢�夊櫒 + 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) { // 鑾峰彇褰撳墠浜嬩欢瀵瑰簲涓婁笅鏂� @@ -101,7 +292,7 @@ var prop = entity.Property(nameof(CommonEntity.CreatedTime)); if (prop != null && prop.CurrentValue?.ToDateTime() == null) { - prop.CurrentValue = DateTime.Now; + prop.CurrentValue = DateTimeOffset.Now; } // 鐢熸垚Id @@ -118,26 +309,33 @@ { prop.CurrentValue = logier?.UserInfoId; } + + // 璧嬪�艰窡韪狪d + prop = entity.Property(nameof(CommonEntity.TraceId)); + if (prop != null) + { + prop.CurrentValue = App.GetTraceId(); + } } else { // 璧嬪�间慨鏀规棩鏈� var prop = entity.Property(nameof(CommonEntity.UpdatedTime)); - if (prop != null && prop.CurrentValue?.ToDateTime() == null) + if (prop != null) { - prop.CurrentValue = DateTime.Now; + prop.CurrentValue = DateTimeOffset.Now; } // 璧嬪�肩敤鎴蜂俊鎭疘d prop = entity.Property(nameof(CommonEntity.UpdatedUserInfoId)); - if (prop != null && prop.CurrentValue == null) + if (prop != null) { prop.CurrentValue = logier?.UserInfoId; } // 璧嬪�艰窡韪狪d prop = entity.Property(nameof(CommonEntity.TraceId)); - if (prop != null && prop.CurrentValue == null) + if (prop != null) { prop.CurrentValue = App.GetTraceId(); } @@ -148,7 +346,7 @@ entity.State = EntityState.Modified; prop = entity.Property(nameof(CommonEntity.IsDeleted)); - if (prop != null && prop.CurrentValue == null) + if (prop != null) { prop.CurrentValue = true; } @@ -178,7 +376,7 @@ : EnumDbAuditOperate.Deleted, TraceId = App.GetTraceId(), CreatedTime = DateTime.Now, - CreatedUserInfoId = logier.UserInfoId, + CreatedUserInfoId = logier?.UserInfoId, }); } } @@ -190,21 +388,6 @@ .ToDictionary(p => p.Metadata.Name, p => getOldValues ? p.OriginalValue : p.CurrentValue); return JsonConvert.SerializeObject(properties); - } - - public static void OnCreating(ModelBuilder modelBuilder, EntityTypeBuilder entityBuilder, DbContext dbContext, Type dbContextLocator) - { - var metadata = entityBuilder.Metadata; - - var parameter = Expression.Parameter(metadata.ClrType, "e"); - var property = Expression.Property(parameter, nameof(CommonEntity.IsDeleted)); - var falseConstant = Expression.Constant(false, typeof(bool)); - var fakeDeleteQueryFilterExpression = Expression.Lambda( - Expression.Equal(property, falseConstant), - parameter - ); - if (fakeDeleteQueryFilterExpression == null) return; - entityBuilder.HasQueryFilter(fakeDeleteQueryFilterExpression); } } } -- Gitblit v1.9.1