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);
|
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="TItem"></typeparam>
|
/// <param name="page"></param>
|
/// <param name="q"></param>
|
/// <param name="cancellationToken"></param>
|
/// <returns></returns>
|
public static async Task<PagedListQueryResult<TItem>> GetPagedListAsync<TItem>(
|
this PagedListQueryPageModel page,
|
IQueryable<TItem> q,
|
CancellationToken cancellationToken = default)
|
where TItem : class, new()
|
{
|
q = q.OrderBy(page.OrderInput);
|
var pagedList = await q.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> 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;
|
}
|
|
/// <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 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.CreatedUserInfoId));
|
if (prop != null && prop.CurrentValue == null)
|
{
|
prop.CurrentValue = logier?.UserInfoId;
|
}
|
|
// 赋值跟踪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.UpdatedUserInfoId));
|
if (prop != null)
|
{
|
prop.CurrentValue = logier?.UserInfoId;
|
}
|
|
// 赋值跟踪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;
|
}
|
|
Db.GetRepository<DbAuditLog, LogDbContextLocator>().InsertNow(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,
|
});
|
}
|
}
|
|
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);
|
}
|
}
|
}
|