sunpengfei
2025-08-11 a292dbb9287676bd4e4e516db439901f241bd73a
FlexJobApi.Core/Utils/DbUtils/DbUtils.cs
@@ -1,72 +1,465 @@
using Furion;
using 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;
using System.Collections.Generic;
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<Guid> 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.Id;
            }
            else
            {
                var entity = new TEntity();
                if (update != null) update(entity);
                else 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)
            if (App.GetService<IMigrator>() != null)
            {
                Console.WriteLine($"正在生成表:{entityType.Name}");
                // 获取实体类的XML注释,并设置为表注释
                var entityBuilder = modelBuilder.Entity(entityType);
                string typeComment = (await entityType.GetXmlDocMemberAsync(xmlDoc))?.Summary;
                if (!string.IsNullOrEmpty(typeComment))
                var xmlDoc = await XmlDocUtils.GetXmlDocAsync();
                foreach (var entityType in modelBuilder.Model.GetEntityTypes())
                {
                    Console.WriteLine($"正在生成表注释:{entityType.Name}-{typeComment}");
                    entityBuilder.ToTable(it => it.HasComment(typeComment));
                }
                // 获取实体属性的XML注释,并设置为列注释
                var properties = entityType.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($"正在生成表:{entityType.Name}");
                    // 获取实体类的XML注释,并设置为表注释
                    var entityBuilder = modelBuilder.Entity(entityType.ClrType);
                    string typeComment = (await entityType.ClrType.GetXmlDocMemberAsync(xmlDoc))?.Summary;
                    if (!string.IsNullOrEmpty(typeComment))
                    {
                        Console.WriteLine($"正在生成属性注释:{property.Name}-{propComment}");
                        entityBuilder.Property(property.Name).HasComment(propComment);
                        Console.WriteLine($"正在生成表注释:{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($"正在生成属性注释:{property.Name}-{propComment}");
                            entityBuilder.Property(property.Name).HasComment(propComment);
                        }
                    }
                }
            }
            foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            {
                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);
                }
            }
        }
        /// <summary>
        /// 数据变更事件
        /// </summary>
        /// <param name="eventData"></param>
        /// <param name="result"></param>
        public static void SavingChangesEvent(DbContextEventData eventData, InterceptionResult<int> result)
        {
            // 获取当前事件对应上下文
@@ -88,6 +481,7 @@
            // 通过请求中获取当前操作人
            var logier = JwtUtils.GetCurrentLogier();
            var traceId = App.GetTraceId();
            // 获取所有已更改的实体
            foreach (var entity in entities)
@@ -113,33 +507,47 @@
                    }
                    // 赋值用户信息Id
                    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;
                    }
                    // 赋值企业Id
                    prop = entity.Property(nameof(CommonEntity.CreatedEnterpriseId));
                    if (prop != null && prop.CurrentValue == null)
                    {
                        prop.CurrentValue = logier?.EnterpriseId;
                    }
                    // 赋值跟踪Id
                    prop = entity.Property(nameof(CommonEntity.TraceId));
                    if (prop != null && prop.CurrentValue == null && traceId.IsNotNull())
                    {
                        prop.CurrentValue = traceId;
                    }
                }
                else
                {
                    // 赋值修改日期
                    var prop = entity.Property(nameof(CommonEntity.UpdatedTime));
                    if (prop != null && prop.CurrentValue?.ToDateTime() == null)
                    if (prop != null)
                    {
                        prop.CurrentValue = DateTimeOffset.Now;
                    }
                    // 赋值用户信息Id
                    prop = entity.Property(nameof(CommonEntity.UpdatedUserInfoId));
                    if (prop != null && prop.CurrentValue == null)
                    prop = entity.Property(nameof(CommonEntity.UpdatedUserId));
                    if (prop != null)
                    {
                        prop.CurrentValue = logier?.UserInfoId;
                        prop.CurrentValue = logier?.Id;
                    }
                    // 赋值跟踪Id
                    prop = entity.Property(nameof(CommonEntity.TraceId));
                    if (prop != null && prop.CurrentValue == null)
                    if (prop != null && traceId.IsNotNull())
                    {
                        prop.CurrentValue = App.GetTraceId();
                        prop.CurrentValue = traceId;
                    }
                    // 软删除
@@ -148,7 +556,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 +586,8 @@
                        : EnumDbAuditOperate.Deleted,
                    TraceId = App.GetTraceId(),
                    CreatedTime = DateTime.Now,
                    CreatedUserInfoId = logier?.UserInfoId,
                    CreatedUserId = logier?.Id,
                    CreatedEnterpriseId = logier?.EnterpriseId
                });
            }
        }
@@ -190,21 +599,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);
        }
    }
}