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<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>
|
/// <param name="modelBuilder"></param>
|
/// <param name="dbContextLocator"></param>
|
/// <returns></returns>
|
public static async Task BuildEntity(ModelBuilder modelBuilder, Type dbContextLocator = null)
|
{
|
if (App.GetService<IMigrator>() != null)
|
{
|
var xmlDoc = await XmlDocUtils.GetXmlDocAsync();
|
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
{
|
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($"正在生成表注释:{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)
|
{
|
// 获取当前事件对应上下文
|
var dbContext = eventData.Context;
|
|
// 强制重新检查一边实体更改信息
|
// dbContext.ChangeTracker.DetectChanges();
|
|
// 获取所有更改,删除,新增的实体
|
var dbAuditLogIgnoreType = typeof(IDbAuditLogIgnore);
|
var physicalDeletionType = typeof(IPhysicalDeletion);
|
var entities = dbContext.ChangeTracker.Entries()
|
.Where(u =>
|
u.GetType() != typeof(DbAuditLog)
|
&& (u.State == EntityState.Modified
|
|| u.State == EntityState.Deleted
|
|| u.State == EntityState.Added))
|
.ToList();
|
|
// 通过请求中获取当前操作人
|
var logier = JwtUtils.GetCurrentLogier();
|
var traceId = App.GetTraceId();
|
|
// 获取所有已更改的实体
|
foreach (var entity in entities)
|
{
|
// 获取实体类型
|
var entityType = entity.Entity.GetType();
|
|
if (entity.State == EntityState.Added)
|
{
|
// 赋值创建日期
|
var prop = entity.Property(nameof(CommonEntity.CreatedTime));
|
if (prop != null && prop.CurrentValue?.ToDateTime() == null)
|
{
|
prop.CurrentValue = DateTimeOffset.Now;
|
}
|
|
// 生成Id
|
prop = entity.Property(nameof(CommonEntity.Id));
|
var defaultValue = Activator.CreateInstance(prop.Metadata.ClrType);
|
if (prop != null && (prop.CurrentValue == null || prop.CurrentValue.Equals(defaultValue)))
|
{
|
prop.CurrentValue = IDGen.NextID();
|
}
|
|
// 赋值用户信息Id
|
prop = entity.Property(nameof(CommonEntity.CreatedUserId));
|
if (prop != null && prop.CurrentValue == null)
|
{
|
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 = DateTimeOffset.Now;
|
}
|
|
// 赋值用户信息Id
|
prop = entity.Property(nameof(CommonEntity.UpdatedUserId));
|
if (prop != null)
|
{
|
prop.CurrentValue = logier?.Id;
|
}
|
|
// 赋值跟踪Id
|
prop = entity.Property(nameof(CommonEntity.TraceId));
|
if (prop != null && traceId.IsNotNull())
|
{
|
prop.CurrentValue = traceId;
|
}
|
|
// 软删除
|
if (entity.State == EntityState.Deleted && !physicalDeletionType.IsAssignableFrom(entityType))
|
{
|
entity.State = EntityState.Modified;
|
|
prop = entity.Property(nameof(CommonEntity.IsDeleted));
|
if (prop != null)
|
{
|
prop.CurrentValue = true;
|
}
|
}
|
}
|
|
if (dbAuditLogIgnoreType.IsAssignableFrom(entityType))
|
{
|
continue;
|
}
|
|
var log = new DbAuditLog
|
{
|
Id = IDGen.NextID(),
|
TableName = entityType.Name,
|
PrimaryKey = (Guid)entity.Property("Id").CurrentValue,
|
TraceId = App.GetTraceId(),
|
CreatedTime = DateTime.Now,
|
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);
|
}
|
}
|
}
|
}
|