Compare commits

...

24 Commits

Author SHA1 Message Date
橙子
5dfaf75440 feat: 新增实体领域事件 2024-12-10 22:04:46 +08:00
橙子
6be5398114 perf: 优化等级处理 2024-12-08 03:43:04 +08:00
chenchun
3932b24fda fix: 修复审计日志无工作单元 2024-12-02 10:11:25 +08:00
橙子
356938d6d3 pref: 优化db工作单元 2024-11-30 23:45:19 +08:00
chenchun
1090907178 perf: 优化动态api启动 2024-11-29 18:01:54 +08:00
chenchun
da2f7073f9 style: 优化abp-cli提示 2024-11-29 15:25:16 +08:00
chenchun
f656ec32c1 perf: 优化在线hub 2024-11-29 14:58:10 +08:00
chenchun
1cc0ef916f feat:去除SqlSugarDbContextFactory的缓存 2024-11-20 16:11:33 +08:00
chenchun
5d793344cd fix: 修复移除属性注入 2024-11-20 13:43:35 +08:00
橙子
bcaca0b782 Merge branch 'refs/heads/multipleDbContext' into abp
# Conflicts:
#	Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs
2024-11-19 21:44:57 +08:00
橙子
5cee7319c6 update Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlsugarCoreExtensions.cs.
Signed-off-by: 橙子 <454313500@qq.com>
2024-11-19 13:43:25 +00:00
chenchun
e960db0d3e feat: 完成hangfire支持工作单元 2024-11-19 18:38:58 +08:00
chenchun
eb2c05e9df feat: 完成支持多db模式 2024-11-19 16:36:33 +08:00
chenchun
353a6b9d0c feat: 完成dbfactory搭建 2024-11-19 12:05:26 +08:00
chenchun
5d2d269f11 feat: 完成多db功能搭建 2024-11-19 11:53:57 +08:00
chenchun
9acb157fae feat: 完成ISqlSugarDbContextDependencies抽离 2024-11-19 11:19:13 +08:00
橙子
4198b53996 feat: 搭建多dbcontext模式 2024-11-18 00:03:20 +08:00
橙子
fdec9ed6b8 feat: 支持hangfire验证 2024-11-16 13:10:06 +08:00
橙子
84cd83894b fix: 修复authservice状态问题 2024-11-16 11:07:22 +08:00
橙子
18dd177961 fix: 修复hangfire ufc时间问题 2024-11-16 11:01:06 +08:00
橙子
41f91ea12d Merge remote-tracking branch 'origin/abp' into abp 2024-11-15 21:38:26 +08:00
橙子
91bf5f93cd feat: bbs评论为空验证 2024-11-15 21:38:18 +08:00
橙子
9445fa8005 !78 修复移动端模式,菜单无法显示问题
Merge pull request !78 from Po/N/A
2024-11-15 13:33:22 +00:00
Po
6b491d1246 修复移动端模式,菜单无法显示问题
Signed-off-by: Po <448443959@qq.com>
2024-11-15 13:07:21 +00:00
37 changed files with 1277 additions and 940 deletions

View File

@@ -9,18 +9,20 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen; using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Options;
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{ {
public static class SwaggerAddExtensions public static class SwaggerAddExtensions
{ {
public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services, Action<SwaggerGenOptions>? action=null) public static IServiceCollection AddYiSwaggerGen<Program>(this IServiceCollection services,
Action<SwaggerGenOptions>? action = null)
{ {
var mvcOptions = services.GetPreConfigureActions<AbpAspNetCoreMvcOptions>().Configure();
var serviceProvider = services.BuildServiceProvider(); var mvcSettings =
var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>(); mvcOptions.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
services.AddAbpSwaggerGen( services.AddAbpSwaggerGen(
@@ -36,7 +38,8 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{ {
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName)) if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{ {
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" }); options.SwaggerDoc(setting.RemoteServiceName,
new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
} }
} }
@@ -45,12 +48,15 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{ {
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor) if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{ {
var settingOrNull = mvcSettings.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly).FirstOrDefault(); var settingOrNull = mvcSettings
.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly)
.FirstOrDefault();
if (settingOrNull is not null) if (settingOrNull is not null)
{ {
return docName == settingOrNull.RemoteServiceName; return docName == settingOrNull.RemoteServiceName;
} }
} }
return false; return false;
}); });
@@ -87,7 +93,6 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
); );
return services; return services;
} }
} }
@@ -103,7 +108,6 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
/// </summary> /// </summary>
/// <param name="model"></param> /// <param name="model"></param>
/// <param name="context"></param> /// <param name="context"></param>
public void Apply(OpenApiSchema model, SchemaFilterContext context) public void Apply(OpenApiSchema model, SchemaFilterContext context)
{ {
if (context.Type.IsEnum) if (context.Type.IsEnum)
@@ -121,9 +125,10 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
Enum e = (Enum)Enum.Parse(context.Type, name); Enum e = (Enum)Enum.Parse(context.Type, name);
var descrptionOrNull = GetEnumDescription(e); var descrptionOrNull = GetEnumDescription(e);
model.Enum.Add(new OpenApiString(name)); model.Enum.Add(new OpenApiString(name));
stringBuilder.Append($"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />"); stringBuilder.Append(
$"【枚举:{name}{(descrptionOrNull is null ? string.Empty : $"({descrptionOrNull})")}={Convert.ToInt64(Enum.Parse(context.Type, name))}】<br />");
}); });
model.Description= stringBuilder.ToString(); model.Description = stringBuilder.ToString();
} }
} }
@@ -133,13 +138,13 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : null; return attributes.Length > 0 ? attributes[0].Description : null;
} }
} }
public class AddRequiredHeaderParameter : IOperationFilter public class AddRequiredHeaderParameter : IOperationFilter
{ {
public static string HeaderKey { get; set; } = "__tenant"; public static string HeaderKey { get; set; } = "__tenant";
public void Apply(OpenApiOperation operation, OperationFilterContext context) public void Apply(OpenApiOperation operation, OperationFilterContext context)
{ {
if (operation.Parameters == null) if (operation.Parameters == null)
@@ -150,7 +155,7 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
In = ParameterLocation.Header, In = ParameterLocation.Header,
Required = false, Required = false,
AllowEmptyValue = true, AllowEmptyValue = true,
Description="租户id或者租户名称可空为默认租户" Description = "租户id或者租户名称可空为默认租户"
}); });
} }
} }

View File

@@ -0,0 +1,45 @@
using Hangfire.Server;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
{
private const string CurrentJobUow = "HangfireUnitOfWork";
private readonly IUnitOfWorkManager _unitOfWorkManager;
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
{
_unitOfWorkManager = unitOfWorkManager;
}
public void OnPerforming(PerformingContext context)
{
var uow = _unitOfWorkManager.Begin();
context.Items.Add(CurrentJobUow, uow);
}
public void OnPerformed(PerformedContext context)
{
AsyncHelper.RunSync(()=>OnPerformedAsync(context));
}
private async Task OnPerformedAsync(PerformedContext context)
{
if (context.Items.TryGetValue(CurrentJobUow, out var obj)
&& obj is IUnitOfWork uow)
{
if (context.Exception == null && !uow.IsCompleted)
{
await uow.CompleteAsync();
}
else
{
await uow.RollbackAsync();
}
uow.Dispose();
}
}
}

View File

@@ -1,11 +1,12 @@
using Microsoft.Extensions.DependencyInjection; using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.BackgroundWorkers.Hangfire;
namespace Yi.Framework.BackgroundWorkers.Hangfire; namespace Yi.Framework.BackgroundWorkers.Hangfire;
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))] [DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
public class YiFrameworkBackgroundWorkersHangfireModule:AbpModule public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
@@ -20,8 +21,15 @@ public class YiFrameworkBackgroundWorkersHangfireModule:AbpModule
foreach (var work in works) foreach (var work in works)
{ {
//如果为空默认使用服务器本地utc时间
work.TimeZone ??= TimeZoneInfo.Local;
await backgroundWorkerManager.AddAsync(work); await backgroundWorkerManager.AddAsync(work);
} }
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
var services = context.ServiceProvider;
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
} }
} }

View File

@@ -0,0 +1,122 @@
using Hangfire.Dashboard;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class YiTokenAuthorizationFilter : IDashboardAsyncAuthorizationFilter, ITransientDependency
{
private const string Bearer = "Bearer: ";
private string RequireUser { get; set; } = "cc";
private TimeSpan ExpiresTime { get; set; } = TimeSpan.FromMinutes(10);
private IServiceProvider _serviceProvider;
public YiTokenAuthorizationFilter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public YiTokenAuthorizationFilter SetRequireUser(string userName)
{
RequireUser = userName;
return this;
}
public YiTokenAuthorizationFilter SetExpiresTime(TimeSpan expiresTime)
{
ExpiresTime = expiresTime;
return this;
}
public bool Authorize(DashboardContext context)
{
var httpContext = context.GetHttpContext();
var _currentUser = _serviceProvider.GetRequiredService<ICurrentUser>();
//如果验证通过设置cookies
if (_currentUser.IsAuthenticated)
{
var cookieOptions = new CookieOptions
{
Expires = DateTimeOffset.Now + ExpiresTime, // 设置 cookie 过期时间,10分钟
};
var authorization = httpContext.Request.Headers["Authorization"].ToString();
if (!string.IsNullOrWhiteSpace(authorization))
{
var token = httpContext.Request.Headers["Authorization"].ToString().Substring(Bearer.Length - 1);
httpContext.Response.Cookies.Append("Token", token, cookieOptions);
}
if (_currentUser.UserName == RequireUser)
{
return true;
}
}
SetChallengeResponse(httpContext);
return false;
}
private void SetChallengeResponse(HttpContext httpContext)
{
httpContext.Response.StatusCode = 401;
httpContext.Response.ContentType = "text/html; charset=utf-8";
string html = """
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Token </title>
<script>
function sendToken() {
// 获取输入的 token
var token = document.getElementById("tokenInput").value;
// 构建请求 URL
var url = "/hangfire";
// 发送 GET 请求
fetch(url,{
headers: {
'Content-Type': 'application/json', // 设置内容类型为 JSON
'Authorization': 'Bearer '+encodeURIComponent(token), // 设置授权头,例如使用 Bearer token
},
})
.then(response => {
if (response.ok) {
return response.text(); // 或使用 response.json() 如果返回的是 JSON
}
throw new Error('Network response was not ok.');
})
.then(data => {
// 处理成功返回的数据
document.open();
document.write(data);
document.close();
})
.catch(error => {
// 处理错误
console.error('There has been a problem with your fetch operation:', error);
alert("请求失败: " + error.message);
});
}
</script>
</head>
<body style="text-align: center;">
<h1>Yi-hangfire</h1>
<h1>Token</h1>
<input type="text" id="tokenInput" placeholder="请输入 token" />
<button onclick="sendToken()"></button>
</body>
</html>
""";
httpContext.Response.WriteAsync(html);
}
public Task<bool> AuthorizeAsync(DashboardContext context)
{
return Task.FromResult(Authorize(context));
}
}

View File

@@ -1,4 +1,5 @@
using SqlSugar; using SqlSugar;
using ArgumentException = System.ArgumentException;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
@@ -53,6 +54,5 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
/// 开启Saas多租户 /// 开启Saas多租户
/// </summary> /// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false; public bool EnabledSaasMultiTenancy { get; set; } = false;
} }
} }

View File

@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
public interface ISqlSugarDbConnectionCreator
{
DbConnOptions Options { get; }
Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
Action<object, DataAfterModel> DataExecuted { get; set; }
Action<object, DataFilterModel> DataExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuted { get; set; }
Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
ConnectionConfig Build(Action<ConnectionConfig>? action = null);
void SetDbAop(ISqlSugarClient currentDb);
}
}

View File

@@ -10,14 +10,14 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
public interface ISqlSugarDbContext public interface ISqlSugarDbContext
{ {
// IAbpLazyServiceProvider LazyServiceProvider { get; set; } /// <summary>
/// SqlSugarDb
/// </summary>
ISqlSugarClient SqlSugarClient { get; } ISqlSugarClient SqlSugarClient { get; }
DbConnOptions Options { get; }
/// <summary> /// <summary>
/// 数据库备份 /// 数据库备份
/// </summary> /// </summary>
void BackupDataBase(); void BackupDataBase();
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
} }
} }

View File

@@ -0,0 +1,21 @@
using System.Reflection;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions;
public interface ISqlSugarDbContextDependencies
{
/// <summary>
/// 执行顺序
/// </summary>
int ExecutionOrder { get; }
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
void DataExecuted(object oldValue, DataAfterModel entityInfo);
void DataExecuting(object oldValue, DataFilterModel entityInfo);
void OnLogExecuting(string sql, SugarParameter[] pars);
void OnLogExecuted(string sql, SugarParameter[] pars);
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
}

View File

@@ -0,0 +1,290 @@
using System.Collections;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Uow;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore;
public class DefaultSqlSugarDbContext : SqlSugarDbContext
{
protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
public IUnitOfWorkManager UnitOfWorkManager => LazyServiceProvider.LazyGetRequiredService<IUnitOfWorkManager>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
protected IEntityChangeEventHelper EntityChangeEventHelper =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
public DefaultSqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
{
}
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
if (IsSoftDeleteFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
}
if (IsMultiTenantFilterEnabled)
{
//表达式里只能有具体值,不能运算
var expressionCurrentTenant = CurrentTenant.Id ?? null;
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
}
}
public override void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
//审计日志
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
if (!DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
break;
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
//类型为guid
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
//为空或者为默认最小值
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
//类型为guid
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id is not null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{
if (CurrentTenant.Id is not null)
{
entityInfo.SetValue(CurrentTenant.Id);
}
}
break;
}
//实体变更领域事件
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
}
break;
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//软删除,发布的是删除事件
if (entityInfo.EntityValue is ISoftDelete softDelete)
{
if (softDelete.IsDeleted == true)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
break;
case DataFilterType.DeleteByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//这里sqlsugar有个特殊删除会返回批量的结果
if (entityInfo.EntityValue is IEnumerable entityValues)
{
foreach (var entityValue in entityValues)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
}
}
}
break;
}
//实体领域事件-所有操作类型
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
var eventReport = CreateEventReport(entityInfo.EntityValue);
PublishEntityEvents(eventReport);
}
}
public override void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
Logger.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
}
}
public override void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
entityColumnInfo.IsEnableUpdateVersionValidation = true;
}
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
{
entityColumnInfo.IsIgnore = true;
}
if (propertyInfo.Name == nameof(Entity<object>.Id))
{
entityColumnInfo.IsPrimarykey = true;
}
}
/// <summary>
/// 创建领域事件报告
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
protected virtual EntityEventReport? CreateEventReport(object entity)
{
var eventReport = new EntityEventReport();
//判断是否为领域事件-聚合根
var generatesDomainEventsEntity = entity as IGeneratesDomainEvents;
if (generatesDomainEventsEntity == null)
{
return eventReport;
}
var localEvents = generatesDomainEventsEntity.GetLocalEvents()?.ToArray();
if (localEvents != null && localEvents.Any())
{
eventReport.DomainEvents.AddRange(
localEvents.Select(
eventRecord => new DomainEventEntry(
entity,
eventRecord.EventData,
eventRecord.EventOrder
)
)
);
generatesDomainEventsEntity.ClearLocalEvents();
}
var distributedEvents = generatesDomainEventsEntity.GetDistributedEvents()?.ToArray();
if (distributedEvents != null && distributedEvents.Any())
{
eventReport.DistributedEvents.AddRange(
distributedEvents.Select(
eventRecord => new DomainEventEntry(
entity,
eventRecord.EventData,
eventRecord.EventOrder)
)
);
generatesDomainEventsEntity.ClearDistributedEvents();
}
return eventReport;
}
/// <summary>
/// 发布领域事件
/// </summary>
/// <param name="changeReport"></param>
private void PublishEntityEvents(EntityEventReport changeReport)
{
foreach (var localEvent in changeReport.DomainEvents)
{
UnitOfWorkManager.Current?.AddOrReplaceLocalEvent(
new UnitOfWorkEventRecord(localEvent.EventData.GetType(), localEvent.EventData, localEvent.EventOrder)
);
}
foreach (var distributedEvent in changeReport.DistributedEvents)
{
UnitOfWorkManager.Current?.AddOrReplaceDistributedEvent(
new UnitOfWorkEventRecord(distributedEvent.EventData.GetType(), distributedEvent.EventData, distributedEvent.EventOrder)
);
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore
{
public static class SqlSugarCoreExtensions
{
/// <summary>
/// 新增db对象可支持多个
/// </summary>
/// <param name="service"></param>
/// <param name="serviceLifetime"></param>
/// <typeparam name="TDbContext"></typeparam>
/// <returns></returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies
{
service.AddTransient<ISqlSugarDbContextDependencies, TDbContext>();
return service;
}
/// <summary>
/// 新增db对象可支持多个
/// </summary>
/// <param name="service"></param>
/// <param name="options"></param>
/// <typeparam name="TDbContext"></typeparam>
/// <returns></returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, Action<DbConnOptions> options) where TDbContext : class, ISqlSugarDbContextDependencies
{
service.Configure<DbConnOptions>(options.Invoke);
service.AddYiDbContext<TDbContext>();
return service;
}
}
}

View File

@@ -1,123 +0,0 @@
using System.Reflection;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore
{
public class SqlSugarDbConnectionCreator: ISqlSugarDbConnectionCreator,ITransientDependency
{
public SqlSugarDbConnectionCreator(IOptions<DbConnOptions> options)
{
Options = options.Value;
}
public DbConnOptions Options { get; }
public void SetDbAop(ISqlSugarClient currentDb)
{
currentDb.Aop.OnLogExecuting = this.OnLogExecuting;
currentDb.Aop.OnLogExecuted = this.OnLogExecuted;
currentDb.Aop.DataExecuting = this.DataExecuting;
currentDb.Aop.DataExecuted = this.DataExecuted;
OnSqlSugarClientConfig(currentDb);
}
public ConnectionConfig Build(Action<ConnectionConfig>? action=null)
{
var dbConnOptions = Options;
#region options
if (dbConnOptions.DbType is null)
{
throw new ArgumentException("DbType配置为空");
}
var slavaConFig = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException("读写分离为空");
}
var readCon = dbConnOptions.ReadUrl;
readCon.ForEach(s =>
{
//如果是动态saas分库这里的连接串都不能写死需要动态添加这里只配置共享库的连接
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
});
}
#endregion
#region config
var connectionConfig = new ConnectionConfig()
{
ConfigId= ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
SlaveConnectionConfigs = slavaConFig,
//设置codefirst非空值判断
ConfigureExternalServices = new ConfigureExternalServices
{
// 处理表
EntityNameService = (type, entity) =>
{
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);// 驼峰转下划线
},
EntityService = (c, p) =>
{
if (new NullabilityInfoContext()
.Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName);// 驼峰转下划线
EntityService(c, p);
}
},
//这里多租户有个坑,无效的
AopEvents = new AopEvents
{
DataExecuted = DataExecuted,
DataExecuting = DataExecuting,
OnLogExecuted = OnLogExecuted,
OnLogExecuting = OnLogExecuting
}
};
if (action is not null)
{
action.Invoke(connectionConfig);
}
#endregion
return connectionConfig;
}
[DisablePropertyInjection]
public Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
[DisablePropertyInjection]
public Action<object, DataAfterModel> DataExecuted { get; set; }
[DisablePropertyInjection]
public Action<object, DataFilterModel> DataExecuting { get; set; }
[DisablePropertyInjection]
public Action<string, SugarParameter[]> OnLogExecuting { get; set; }
[DisablePropertyInjection]
public Action<string, SugarParameter[]> OnLogExecuted { get; set; }
[DisablePropertyInjection]
public Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
}
}

View File

@@ -1,379 +1,49 @@
using System.Collections; using System.Reflection;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar; using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore namespace Yi.Framework.SqlSugarCore;
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
{ {
public class SqlSugarDbContext : ISqlSugarDbContext protected IAbpLazyServiceProvider LazyServiceProvider { get; }
{
/// <summary>
/// SqlSugar 客户端
/// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; }
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
private IAbpLazyServiceProvider LazyServiceProvider { get; }
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
private ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
private IEntityChangeEventHelper EntityChangeEventHelper =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
}
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{ {
LazyServiceProvider = lazyServiceProvider; this.LazyServiceProvider = lazyServiceProvider;
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig;
connectionCreator.EntityService = EntityService;
connectionCreator.DataExecuting = DataExecuting;
connectionCreator.DataExecuted = DataExecuted;
connectionCreator.OnLogExecuting = OnLogExecuting;
connectionCreator.OnLogExecuted = OnLogExecuted;
SqlSugarClient = new SqlSugarClient(connectionCreator.Build(action: options =>
{
options.ConnectionString = GetCurrentConnectionString();
options.DbType = GetCurrentDbType();
}));
//统一使用aop处理
connectionCreator.SetDbAop(SqlSugarClient);
//替换默认序列化器
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
}
/// <summary>
/// db切换多库支持
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentConnectionString()
{
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
var connectionString =
connectionStringResolver.ResolveAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (string.IsNullOrWhiteSpace(connectionString))
{
Check.NotNull(Options.Url, "dbUrl未配置");
}
return connectionString!;
}
protected virtual DbType GetCurrentDbType()
{
if (CurrentTenant.Name is not null)
{
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
if (dbTypeFromTenantName is not null)
{
return dbTypeFromTenantName.Value;
}
}
Check.NotNull(Options.DbType, "默认DbType未配置");
return Options.DbType!.Value;
}
//根据租户name进行匹配db类型: Test_Sqlite[来自AI]
private DbType? GetDbTypeFromTenantName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// 查找下划线的位置
int underscoreIndex = name.LastIndexOf('_');
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
{
return null;
}
// 提取 枚举 部分
string enumString = name.Substring(underscoreIndex + 1);
// 尝试将 尾缀 转换为枚举
if (Enum.TryParse<DbType>(enumString, out DbType result))
{
return result;
}
// 条件不满足时返回 null
return null;
} }
/// <summary> protected ISqlSugarClient SqlSugarClient { get;private set; }
/// 上下文对象扩展 public int ExecutionOrder => 0;
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
//需自定义扩展
if (IsSoftDeleteFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
}
if (IsMultiTenantFilterEnabled) public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{ {
//表达式里只能有具体值,不能运算 SqlSugarClient = sqlSugarClient;
var expressionCurrentTenant = CurrentTenant.Id ?? null;
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
}
CustomDataFilter(sqlSugarClient); CustomDataFilter(sqlSugarClient);
} }
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient) protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{ {
} }
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo) public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{ {
} }
/// <summary> public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
/// 数据
/// </summary>
/// <param name="oldValue"></param>
/// <param name="entityInfo"></param>
protected virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{ {
//审计日志
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
if (!DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
} }
break; public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{ {
//类型为guid
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
} }
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime))) public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{ {
//为空或者为默认最小值
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
//类型为guid
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id is not null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
} }
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId))) public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{ {
if (CurrentTenant.Id is not null)
{
entityInfo.SetValue(CurrentTenant.Id);
}
}
break;
}
//领域事件
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
}
break;
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//软删除,发布的是删除事件
if (entityInfo.EntityValue is ISoftDelete softDelete)
{
if (softDelete.IsDeleted == true)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
break;
case DataFilterType.DeleteByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//这里sqlsugar有个特殊删除会返回批量的结果
if (entityInfo.EntityValue is IEnumerable entityValues)
{
foreach (var entityValue in entityValues)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
}
}
}
break;
}
}
/// <summary>
/// 日志
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
protected virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
}
}
/// <summary>
/// 日志
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
protected virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
/// <summary>
/// 实体配置
/// </summary>
/// <param name="property"></param>
/// <param name="column"></param>
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
{
if (property.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
column.IsEnableUpdateVersionValidation = true;
}
if (property.PropertyType == typeof(ExtraPropertyDictionary))
{
column.IsIgnore = true;
}
if (property.Name == nameof(Entity<object>.Id))
{
column.IsPrimarykey = true;
}
}
public void BackupDataBase()
{
string directoryName = "database_backup";
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
switch (Options.DbType)
{
case DbType.MySql:
//MySql
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
break;
case DbType.Sqlite:
//Sqlite
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
break;
case DbType.SqlServer:
//SqlServer
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
break;
default:
throw new NotImplementedException("其他数据库备份未实现");
}
}
} }
} }

View File

@@ -0,0 +1,290 @@
using System.Collections.Concurrent;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore
{
public class SqlSugarDbContextFactory : ISqlSugarDbContext
{
/// <summary>
/// SqlSugar 客户端
/// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; }
private IAbpLazyServiceProvider LazyServiceProvider { get; }
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
var connectionString = GetCurrentConnectionString();
var connectionConfig =BuildConnectionConfig(action: options =>
{
options.ConnectionString = connectionString;
options.DbType = GetCurrentDbType();
});
// var connectionConfig = ConnectionConfigCache.GetOrAdd(connectionString, (_) =>
// BuildConnectionConfig(action: options =>
// {
// options.ConnectionString = connectionString;
// options.DbType = GetCurrentDbType();
// }));
SqlSugarClient = new SqlSugarClient(connectionConfig);
//生命周期以下都可以直接使用sqlsugardb了
// Aop及多租户连接字符串和类型需要单独设置
// Aop操作不能进行缓存
SetDbAop(SqlSugarClient);
}
/// <summary>
/// 构建Aop-sqlsugaraop在多租户模式中需单独设置
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient)
{
//替换默认序列化器
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
//将所有ISqlSugarDbContextDependencies进行累加
Action<string, SugarParameter[]> onLogExecuting = null;
Action<string, SugarParameter[]> onLogExecuted = null;
Action<object, DataFilterModel> dataExecuting = null;
Action<object, DataAfterModel> dataExecuted = null;
Action<ISqlSugarClient> onSqlSugarClientConfig = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
onLogExecuting += dependency.OnLogExecuting;
onLogExecuted += dependency.OnLogExecuted;
dataExecuting += dependency.DataExecuting;
dataExecuted += dependency.DataExecuted;
onSqlSugarClientConfig += dependency.OnSqlSugarClientConfig;
}
//最先存放db操作
onSqlSugarClientConfig(sqlSugarClient);
sqlSugarClient.Aop.OnLogExecuting =onLogExecuting;
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
sqlSugarClient.Aop.DataExecuting =dataExecuting;
sqlSugarClient.Aop.DataExecuted =dataExecuted;
}
/// <summary>
/// 构建连接配置
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig>? action = null)
{
var dbConnOptions = Options;
#region options
if (dbConnOptions.DbType is null)
{
throw new ArgumentException("DbType配置为空");
}
var slavaConFig = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException("读写分离为空");
}
var readCon = dbConnOptions.ReadUrl;
readCon.ForEach(s =>
{
//如果是动态saas分库这里的连接串都不能写死需要动态添加这里只配置共享库的连接
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
});
}
#endregion
#region config
var connectionConfig = new ConnectionConfig()
{
ConfigId = ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
SlaveConnectionConfigs = slavaConFig,
//设置codefirst非空值判断
ConfigureExternalServices = new ConfigureExternalServices
{
// 处理表
EntityNameService = (type, entity) =>
{
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
},
EntityService = (c, p) =>
{
if (new NullabilityInfoContext()
.Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName); // 驼峰转下划线
//将所有ISqlSugarDbContextDependencies的EntityService进行累加
//额外的实体服务需要这里配置,
Action<PropertyInfo, EntityColumnInfo> entityService = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
entityService += dependency.EntityService;
}
entityService(c, p);
}
},
//这里多租户有个坑,这里配置是无效的
// AopEvents = new AopEvents
// {
// DataExecuted = DataExecuted,
// DataExecuting = DataExecuting,
// OnLogExecuted = OnLogExecuted,
// OnLogExecuting = OnLogExecuting
// }
};
if (action is not null)
{
action.Invoke(connectionConfig);
}
#endregion
return connectionConfig;
}
/// <summary>
/// db切换多库支持
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentConnectionString()
{
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
var connectionString =
AsyncHelper.RunSync(() => connectionStringResolver.ResolveAsync());
if (string.IsNullOrWhiteSpace(connectionString))
{
Check.NotNull(Options.Url, "dbUrl未配置");
}
return connectionString!;
}
protected virtual DbType GetCurrentDbType()
{
if (CurrentTenant.Name is not null)
{
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
if (dbTypeFromTenantName is not null)
{
return dbTypeFromTenantName.Value;
}
}
Check.NotNull(Options.DbType, "默认DbType未配置");
return Options.DbType!.Value;
}
//根据租户name进行匹配db类型: Test_Sqlite[来自AI]
private DbType? GetDbTypeFromTenantName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// 查找下划线的位置
int underscoreIndex = name.LastIndexOf('_');
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
{
return null;
}
// 提取 枚举 部分
string enumString = name.Substring(underscoreIndex + 1);
// 尝试将 尾缀 转换为枚举
if (Enum.TryParse<DbType>(enumString, out DbType result))
{
return result;
}
// 条件不满足时返回 null
return null;
}
public virtual void BackupDataBase()
{
string directoryName = "database_backup";
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
switch (Options.DbType)
{
case DbType.MySql:
//MySql
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
break;
case DbType.Sqlite:
//Sqlite
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
break;
case DbType.SqlServer:
//SqlServer
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
break;
default:
throw new NotImplementedException("其他数据库备份未实现");
}
}
}
}

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore
{
public static class SqlsugarCoreExtensions
{
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
{
service.Replace(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
return service;
}
public static IServiceCollection TryAddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
{
service.TryAdd(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime));
return service;
}
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, Action<DbConnOptions> options) where DbContext : class, ISqlSugarDbContext
{
service.Configure<DbConnOptions>(ops =>
{
options.Invoke(ops);
});
service.AddYiDbContext<DbContext>();
return service;
}
}
}

View File

@@ -13,8 +13,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
{ {
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
{ {
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; } public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
public IServiceProvider ServiceProvider { get; set; } public IServiceProvider ServiceProvider { get; set; }
@@ -28,8 +26,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver, IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant, ICurrentTenant currentTenant
ISqlSugarDbConnectionCreator dbConnectionCreator
) )
{ {
UnitOfWorkManager = unitOfWorkManager; UnitOfWorkManager = unitOfWorkManager;
@@ -37,10 +34,8 @@ namespace Yi.Framework.SqlSugarCore.Uow
CancellationTokenProvider = cancellationTokenProvider; CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance; Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
_dbConnectionCreator = dbConnectionCreator;
} }
//private static object _databaseApiLock = new object();
public virtual async Task<TDbContext> GetDbContextAsync() public virtual async Task<TDbContext> GetDbContextAsync()
{ {
@@ -52,19 +47,16 @@ namespace Yi.Framework.SqlSugarCore.Uow
var unitOfWork = UnitOfWorkManager.Current; var unitOfWork = UnitOfWorkManager.Current;
if (unitOfWork == null /*|| unitOfWork.Options.IsTransactional == false*/) if (unitOfWork == null )
{ {
var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>(); //var dbContext = (TDbContext)ServiceProvider.GetRequiredService<ISqlSugarDbContext>();
//提高体验,取消工作单元强制性
//throw new AbpException("A DbContext can only be created inside a unit of work!");
//如果不启用工作单元创建一个新的db不开启事务即可 //如果不启用工作单元创建一个新的db不开启事务即可
return dbContext; //return dbContext;
//2024-11-30改回强制性使用工作单元否则容易造成歧义
throw new AbpException("DbContext 只能在工作单元内工作当前DbContext没有工作单元如需创建新线程并发操作请手动创建工作单元");
} }
//尝试当前工作单元获取db //尝试当前工作单元获取db
var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey); var databaseApi = unitOfWork.FindDatabaseApi(dbContextKey);

View File

@@ -6,12 +6,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using SqlSugar; using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.SqlSugarCore.Repositories; using Yi.Framework.SqlSugarCore.Repositories;
using Yi.Framework.SqlSugarCore.Uow; using Yi.Framework.SqlSugarCore.Uow;
@@ -28,7 +25,7 @@ namespace Yi.Framework.SqlSugarCore
var section = configuration.GetSection("DbConnOptions"); var section = configuration.GetSection("DbConnOptions");
Configure<DbConnOptions>(section); Configure<DbConnOptions>(section);
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>(); service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContextFactory>();
//不开放sqlsugarClient //不开放sqlsugarClient
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient); //service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
@@ -47,6 +44,7 @@ namespace Yi.Framework.SqlSugarCore
//将默认db传递给abp连接字符串模块 //将默认db传递给abp连接字符串模块
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; }); Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
context.Services.AddYiDbContext<DefaultSqlSugarDbContext>();
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -72,7 +70,6 @@ namespace Yi.Framework.SqlSugarCore
logger.LogInformation(sb.ToString()); logger.LogInformation(sb.ToString());
//Todo准备支持多租户种子数据及CodeFirst
if (options.EnabledCodeFirst) if (options.EnabledCodeFirst)
{ {

View File

@@ -53,10 +53,10 @@ public class AuditingStore : IAuditingStore, ITransientDependency
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo) protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
{ {
Logger.LogDebug("Yi-请求追踪:" + JsonHelper.ObjToStr(auditInfo, "yyyy-MM-dd HH:mm:ss")); Logger.LogDebug("Yi-请求追踪:" + JsonHelper.ObjToStr(auditInfo, "yyyy-MM-dd HH:mm:ss"));
// using (var uow = UnitOfWorkManager.Begin(true,isTransactional:false)) using (var uow = UnitOfWorkManager.Begin())
// { {
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo)); await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
// await uow.CompleteAsync(); await uow.CompleteAsync();
// } }
} }
} }

View File

@@ -110,7 +110,9 @@ namespace Yi.Framework.Bbs.Application.Services.Analyses
) )
.ToPageListAsync(pageIndex, input.MaxResultCount, total); .ToPageListAsync(pageIndex, input.MaxResultCount, total);
output.ForEach(x => { x.LevelName = _bbsUserManager._levelCacheDic[x.Level].Name; }); var levelCache = await _bbsUserManager.GetLevelCacheMapAsync();
output.ForEach(x => { x.LevelName = levelCache[x.Level].Name; });
return new PagedResultDto<MoneyTopUserDto> return new PagedResultDto<MoneyTopUserDto>
{ {
Items = output, Items = output,

View File

@@ -136,9 +136,9 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
[Authorize] [Authorize]
public override async Task<CommentGetOutputDto> CreateAsync(CommentCreateInputVo input) public override async Task<CommentGetOutputDto> CreateAsync(CommentCreateInputVo input)
{ {
if (input.Content.Length<=6) if (string.IsNullOrWhiteSpace(input.Content)|| input.Content=="<p><br></p>")
{ {
throw new UserFriendlyException("评论长度至少大于6"); throw new UserFriendlyException("评论不能为空");
} }
var discuess = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId); var discuess = await _discussRepository.GetFirstAsync(x => x.Id == input.DiscussId);

View File

@@ -158,10 +158,11 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
//查询完主题之后,要过滤一下私有的主题信息 //查询完主题之后,要过滤一下私有的主题信息
items.ApplyPermissionTypeFilter(CurrentUser.Id ?? Guid.Empty); items.ApplyPermissionTypeFilter(CurrentUser.Id ?? Guid.Empty);
var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
//等级、是否点赞赋值 //等级、是否点赞赋值
items?.ForEach(x => items?.ForEach(x =>
{ {
x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name; x.User.LevelName = levelCacheDic[x.User.Level].Name;
if (CurrentUser.Id is not null) if (CurrentUser.Id is not null)
{ {
//默认fasle //默认fasle
@@ -212,7 +213,8 @@ namespace Yi.Framework.Bbs.Application.Services.Forum
} }
}, true) }, true)
.ToListAsync(); .ToListAsync();
output?.ForEach(x => x.User.LevelName = _bbsUserManager._levelCacheDic[x.User.Level].Name); var levelCacheDic= await _bbsUserManager.GetLevelCacheMapAsync();
output?.ForEach(x => x.User.LevelName = levelCacheDic[x.User.Level].Name);
return output; return output;
} }

View File

@@ -14,7 +14,8 @@ namespace Yi.Framework.Bbs.Domain.Managers
{ {
public ISqlSugarRepository<UserAggregateRoot> _userRepository; public ISqlSugarRepository<UserAggregateRoot> _userRepository;
public ISqlSugarRepository<BbsUserExtraInfoEntity> _bbsUserInfoRepository; public ISqlSugarRepository<BbsUserExtraInfoEntity> _bbsUserInfoRepository;
public Dictionary<int, LevelCacheItem> _levelCacheDic; // public Dictionary<int, LevelCacheItem> _levelCacheDic;
private LevelManager _levelManager;
public BbsUserManager(ISqlSugarRepository<UserAggregateRoot> userRepository, public BbsUserManager(ISqlSugarRepository<UserAggregateRoot> userRepository,
ISqlSugarRepository<BbsUserExtraInfoEntity> bbsUserInfoRepository, ISqlSugarRepository<BbsUserExtraInfoEntity> bbsUserInfoRepository,
@@ -23,7 +24,12 @@ namespace Yi.Framework.Bbs.Domain.Managers
{ {
_userRepository = userRepository; _userRepository = userRepository;
_bbsUserInfoRepository = bbsUserInfoRepository; _bbsUserInfoRepository = bbsUserInfoRepository;
_levelCacheDic = levelManager.GetCacheMapAsync().Result; _levelManager = levelManager;
}
public async Task<Dictionary<int, LevelCacheItem>> GetLevelCacheMapAsync()
{
return await _levelManager.GetCacheMapAsync();
} }
public async Task<BbsUserInfoDto?> GetBbsUserInfoAsync(Guid userId) public async Task<BbsUserInfoDto?> GetBbsUserInfoAsync(Guid userId)
@@ -44,7 +50,8 @@ namespace Yi.Framework.Bbs.Domain.Managers
}, true) }, true)
.FirstAsync(user => user.Id == userId); .FirstAsync(user => user.Id == userId);
userInfo.LevelName = _levelCacheDic[userInfo.Level].Name; var levelCacheDic= await GetLevelCacheMapAsync();
userInfo.LevelName = levelCacheDic[userInfo.Level].Name;
return userInfo; return userInfo;
} }
@@ -66,7 +73,8 @@ namespace Yi.Framework.Bbs.Domain.Managers
DiscussNumber = info.DiscussNumber DiscussNumber = info.DiscussNumber
}, true) }, true)
.ToListAsync(); .ToListAsync();
userInfos?.ForEach(userInfo => userInfo.LevelName = _levelCacheDic[userInfo.Level].Name); var levelCacheDic= await GetLevelCacheMapAsync();
userInfos?.ForEach(userInfo => userInfo.LevelName =levelCacheDic[userInfo.Level].Name);
return userInfos ?? new List<BbsUserInfoDto>(); return userInfos ?? new List<BbsUserInfoDto>();
} }

View File

@@ -355,13 +355,13 @@ namespace Yi.Framework.Rbac.Application.Services
{ {
//将后端菜单转换成前端路由,组件级别需要过滤 //将后端菜单转换成前端路由,组件级别需要过滤
output = output =
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3RuoYiRouterBuild(); ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Ruoyi).ToList()).Vue3RuoYiRouterBuild();
} }
else if (routerType == "pure") else if (routerType == "pure")
{ {
//将后端菜单转换成前端路由,组件级别需要过滤 //将后端菜单转换成前端路由,组件级别需要过滤
output = output =
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3PureRouterBuild(); ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Pure).ToList()).Vue3PureRouterBuild();
} }
return output; return output;

View File

@@ -20,14 +20,15 @@ namespace Yi.Framework.Rbac.Application.Services.Authentication
/// </summary> /// </summary>
public class AuthService : YiCrudAppService<AuthAggregateRoot, AuthOutputDto, Guid, AuthGetListInput> public class AuthService : YiCrudAppService<AuthAggregateRoot, AuthOutputDto, Guid, AuthGetListInput>
{ {
private HttpContext HttpContext { get; set; } private HttpContext? HttpContext { get; set; }
private ILogger<AuthService> _logger; private ILogger<AuthService> _logger;
private ISqlSugarRepository<AuthAggregateRoot, Guid> _repository; private ISqlSugarRepository<AuthAggregateRoot, Guid> _repository;
private IAccountManager _accountManager; private IAccountManager _accountManager;
public AuthService(IAccountManager accountManager, IHttpContextAccessor httpContextAccessor, ILogger<AuthService> logger, ISqlSugarRepository<AuthAggregateRoot, Guid> repository) : base(repository) public AuthService(IAccountManager accountManager, IHttpContextAccessor httpContextAccessor, ILogger<AuthService> logger, ISqlSugarRepository<AuthAggregateRoot, Guid> repository) : base(repository)
{ {
_logger = logger; _logger = logger;
HttpContext = httpContextAccessor.HttpContext ?? throw new ApplicationException("未注册Http"); //可能为空
HttpContext = httpContextAccessor.HttpContext;
_repository = repository; _repository = repository;
_accountManager = accountManager; _accountManager = accountManager;
} }
@@ -79,6 +80,10 @@ namespace Yi.Framework.Rbac.Application.Services.Authentication
private async Task<(string, string)> GetOpenIdAndNameAsync(string scheme) private async Task<(string, string)> GetOpenIdAndNameAsync(string scheme)
{ {
if (HttpContext is null)
{
throw new AggregateException("HttpContext 参数为空");
}
var authenticateResult = await HttpContext.AuthenticateAsync(scheme); var authenticateResult = await HttpContext.AuthenticateAsync(scheme);
if (!authenticateResult.Succeeded) if (!authenticateResult.Succeeded)
{ {

View File

@@ -13,6 +13,7 @@ namespace Yi.Framework.Rbac.Application.Services.Monitor
{ {
private ILogger<OnlineService> _logger; private ILogger<OnlineService> _logger;
private IHubContext<OnlineHub> _hub; private IHubContext<OnlineHub> _hub;
public OnlineService(ILogger<OnlineService> logger, IHubContext<OnlineHub> hub) public OnlineService(ILogger<OnlineService> logger, IHubContext<OnlineHub> hub)
{ {
_logger = logger; _logger = logger;
@@ -26,18 +27,21 @@ namespace Yi.Framework.Rbac.Application.Services.Monitor
/// <returns></returns> /// <returns></returns>
public Task<PagedResultDto<OnlineUserModel>> GetListAsync([FromQuery] OnlineUserModel online) public Task<PagedResultDto<OnlineUserModel>> GetListAsync([FromQuery] OnlineUserModel online)
{ {
var data = OnlineHub.clientUsers; var data = OnlineHub.ClientUsersDic;
IEnumerable<OnlineUserModel> dataWhere = data.AsEnumerable(); IEnumerable<OnlineUserModel> dataWhere = data.Values.AsEnumerable();
if (!string.IsNullOrEmpty(online.Ipaddr)) if (!string.IsNullOrEmpty(online.Ipaddr))
{ {
dataWhere = dataWhere.Where((u) => u.Ipaddr!.Contains(online.Ipaddr)); dataWhere = dataWhere.Where((u) => u.Ipaddr!.Contains(online.Ipaddr));
} }
if (!string.IsNullOrEmpty(online.UserName)) if (!string.IsNullOrEmpty(online.UserName))
{ {
dataWhere = dataWhere.Where((u) => u.UserName!.Contains(online.UserName)); dataWhere = dataWhere.Where((u) => u.UserName!.Contains(online.UserName));
} }
return Task.FromResult(new PagedResultDto<OnlineUserModel>() { TotalCount = data.Count, Items = dataWhere.ToList() });
return Task.FromResult(new PagedResultDto<OnlineUserModel>()
{ TotalCount = data.Count, Items = dataWhere.ToList() });
} }
@@ -50,12 +54,13 @@ namespace Yi.Framework.Rbac.Application.Services.Monitor
[Route("online/{connnectionId}")] [Route("online/{connnectionId}")]
public async Task<bool> ForceOut(string connnectionId) public async Task<bool> ForceOut(string connnectionId)
{ {
if (OnlineHub.clientUsers.Exists(u => u.ConnnectionId == connnectionId)) if (OnlineHub.ClientUsersDic.ContainsKey(connnectionId))
{ {
//前端接受到这个事件后,触发前端自动退出 //前端接受到这个事件后,触发前端自动退出
await _hub.Clients.Client(connnectionId).SendAsync("forceOut", "你已被强制退出!"); await _hub.Clients.Client(connnectionId).SendAsync("forceOut", "你已被强制退出!");
return true; return true;
} }
return false; return false;
} }
} }

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization; using System.Collections.Concurrent;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.SignalR; using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -13,26 +14,27 @@ namespace Yi.Framework.Rbac.Application.SignalRHubs
//[Authorize] //[Authorize]
public class OnlineHub : AbpHub public class OnlineHub : AbpHub
{ {
public static readonly List<OnlineUserModel> clientUsers = new(); public static ConcurrentDictionary<string, OnlineUserModel> ClientUsersDic { get; set; } = new();
private readonly static object objLock = new object();
private HttpContext? _httpContext; private readonly HttpContext? _httpContext;
private ILogger<OnlineHub> _logger => LoggerFactory.CreateLogger<OnlineHub>(); private ILogger<OnlineHub> _logger => LoggerFactory.CreateLogger<OnlineHub>();
public OnlineHub(IHttpContextAccessor httpContextAccessor) public OnlineHub(IHttpContextAccessor httpContextAccessor)
{ {
_httpContext = httpContextAccessor?.HttpContext; _httpContext = httpContextAccessor?.HttpContext;
} }
/// <summary> /// <summary>
/// 成功连接 /// 成功连接
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public override Task OnConnectedAsync() public override Task OnConnectedAsync()
{ {
lock (objLock) if (_httpContext is null)
{ {
return Task.CompletedTask;
}
var name = CurrentUser.UserName; var name = CurrentUser.UserName;
var loginUser = new LoginLogAggregateRoot().GetInfoByHttpContext(_httpContext); var loginUser = new LoginLogAggregateRoot().GetInfoByHttpContext(_httpContext);
@@ -48,18 +50,18 @@ namespace Yi.Framework.Rbac.Application.SignalRHubs
}; };
//已登录 //已登录
if (CurrentUser.Id is not null) if (CurrentUser.IsAuthenticated)
{ //先移除之前的用户id一个用户只能一个 {
clientUsers.RemoveAll(u => u.UserId == CurrentUser.Id); ClientUsersDic.RemoveAll(u => u.Value.UserId == CurrentUser.Id);
_logger.LogInformation($"{DateTime.Now}{name},{Context.ConnectionId}连接服务端success当前已连接{clientUsers.Count}个"); _logger.LogDebug(
$"{DateTime.Now}{name},{Context.ConnectionId}连接服务端success当前已连接{ClientUsersDic.Count}个");
} }
//全部移除之后,再进行添加
clientUsers.RemoveAll(u => u.ConnnectionId == Context.ConnectionId);
clientUsers.Add(user); ClientUsersDic.AddOrUpdate(Context.ConnectionId, user, (_, _) => user);
//当有人加入,向全部客户端发送当前总数 //当有人加入,向全部客户端发送当前总数
Clients.All.SendAsync("onlineNum", clientUsers.Count); Clients.All.SendAsync("onlineNum", ClientUsersDic.Count);
}
return base.OnConnectedAsync(); return base.OnConnectedAsync();
} }
@@ -69,22 +71,17 @@ namespace Yi.Framework.Rbac.Application.SignalRHubs
/// </summary> /// </summary>
/// <param name="exception"></param> /// <param name="exception"></param>
/// <returns></returns> /// <returns></returns>
public override Task OnDisconnectedAsync(Exception exception) public override Task OnDisconnectedAsync(Exception? exception)
{
lock (objLock)
{ {
//已登录 //已登录
if (CurrentUser.Id is not null) if (CurrentUser.IsAuthenticated)
{ {
clientUsers.RemoveAll(u => u.UserId == CurrentUser.Id); ClientUsersDic.RemoveAll(u => u.Value.UserId == CurrentUser.Id);
_logger.LogInformation($"用户{CurrentUser?.UserName}离开了,当前已连接{clientUsers.Count}个"); _logger.LogDebug($"用户{CurrentUser?.UserName}离开了,当前已连接{ClientUsersDic.Count}个");
}
clientUsers.RemoveAll(u => u.ConnnectionId == Context.ConnectionId);
Clients.All.SendAsync("onlineNum", clientUsers.Count);
} }
ClientUsersDic.Remove(Context.ConnectionId, out _);
Clients.All.SendAsync("onlineNum", ClientUsersDic.Count);
return base.OnDisconnectedAsync(exception); return base.OnDisconnectedAsync(exception);
} }
} }
} }

View File

@@ -9,8 +9,8 @@ namespace Yi.Framework.Rbac.Domain.Shared.Dtos
public HashSet<RoleDto> Roles { get; set; } = new(); public HashSet<RoleDto> Roles { get; set; } = new();
public HashSet<MenuDto> Menus { get; set; } = new(); public HashSet<MenuDto> Menus { get; set; } = new();
public List<string> RoleCodes { get; set; } = new(); public HashSet<string> RoleCodes { get; set; } = new();
public List<string> PermissionCodes { get; set; } = new(); public HashSet<string> PermissionCodes { get; set; } = new();
} }
public class UserDto public class UserDto

View File

@@ -217,8 +217,8 @@ namespace Yi.Framework.Rbac.Domain.Managers
} }
else else
{ {
dto.PermissionCodes?.ForEach(per => AddToClaim(claims, TokenTypeConst.Permission, per)); dto.PermissionCodes?.ToList()?.ForEach(per => AddToClaim(claims, TokenTypeConst.Permission, per));
dto.RoleCodes?.ForEach(role => AddToClaim(claims, AbpClaimTypes.Role, role)); dto.RoleCodes?.ToList()?.ForEach(role => AddToClaim(claims, AbpClaimTypes.Role, role));
} }
return claims; return claims;

View File

@@ -15,7 +15,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.TryAddYiDbContext<YiRbacDbContext>(); context.Services.AddYiDbContext<YiRbacDbContext>();
} }
} }
} }

View File

@@ -1,5 +1,8 @@
using SqlSugar; using Microsoft.Extensions.DependencyInjection;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Users;
using Yi.Framework.Rbac.Domain.Authorization; using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Entities; using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.Rbac.Domain.Extensions; using Yi.Framework.Rbac.Domain.Extensions;
@@ -11,22 +14,20 @@ namespace Yi.Framework.Rbac.SqlSugarCore
{ {
public class YiRbacDbContext : SqlSugarDbContext public class YiRbacDbContext : SqlSugarDbContext
{ {
public YiRbacDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
{ protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
}
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient) protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{ {
if (DataFilter.IsEnabled<IDataPermission>()) if (DataFilter.IsEnabled<IDataPermission>())
{ {
DataPermissionFilter(sqlSugarClient); DataPermissionFilter(sqlSugarClient);
} }
base.CustomDataFilter(sqlSugarClient);
} }
public YiRbacDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
{
}
/// <summary> /// <summary>
/// 数据权限过滤 /// 数据权限过滤
/// </summary> /// </summary>
@@ -89,5 +90,6 @@ namespace Yi.Framework.Rbac.SqlSugarCore
sqlSugarClient.QueryFilter.AddTableFilter(expUser.ToExpression()); sqlSugarClient.QueryFilter.AddTableFilter(expUser.ToExpression());
sqlSugarClient.QueryFilter.AddTableFilter(expRole.ToExpression()); sqlSugarClient.QueryFilter.AddTableFilter(expRole.ToExpression());
} }
} }
} }

View File

@@ -67,7 +67,8 @@ namespace Yi.Abp.Application.Services
public async Task GetUowAsync() public async Task GetUowAsync()
{ {
//魔改 //魔改
// 用户体验优先,万金油模式,支持高并发。支持单、多线程并发安全,支持多线程工作单元,支持多线程无工作单元,支持。。。 // 用户体验优先,万金油模式,支持高并发。支持单、多线程并发安全,支持多线程工作单元,支持。。。
// 不支持多线程无工作单元应由工作单元统一管理来自abp工作单元设计
// 请注意如果requiresNew: true只有在没有工作单元内使用嵌套子工作单元默认值false即可 // 请注意如果requiresNew: true只有在没有工作单元内使用嵌套子工作单元默认值false即可
// 自动在各个情况处理db客户端最优解之一 // 自动在各个情况处理db客户端最优解之一
int i = 3; int i = 3;
@@ -78,7 +79,8 @@ namespace Yi.Abp.Application.Services
{ {
tasks.Add(Task.Run(async () => tasks.Add(Task.Run(async () =>
{ {
await sqlSugarRepository.InsertAsync(new BannerAggregateRoot { Name = "插入2" }); //以下操作是错误的不允许在新线程中直接操作db所有db操作应放在工作单元内应由工作单元统一管理-来自abp工作单元设计
//await sqlSugarRepository.InsertAsync(new BannerAggregateRoot { Name = "插入2" });
using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true)) using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: true))
{ {
await sqlSugarRepository.InsertAsync(new BannerAggregateRoot { Name = "插入1" }); await sqlSugarRepository.InsertAsync(new BannerAggregateRoot { Name = "插入1" });
@@ -173,6 +175,5 @@ namespace Yi.Abp.Application.Services
return result ?? string.Empty; return result ?? string.Empty;
} }
} }
} }

View File

@@ -6,7 +6,7 @@ using Yi.Framework.SqlSugarCore;
namespace Yi.Abp.SqlSugarCore namespace Yi.Abp.SqlSugarCore
{ {
public class YiDbContext : YiRbacDbContext public class YiDbContext : SqlSugarDbContext
{ {
public YiDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) public YiDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
{ {

View File

@@ -22,8 +22,6 @@ using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.BackgroundJobs.Hangfire; using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Swashbuckle; using Volo.Abp.Swashbuckle;
@@ -68,6 +66,30 @@ namespace Yi.Abp.Web
{ {
private const string DefaultCorsPolicyName = "Default"; private const string DefaultCorsPolicyName = "Default";
public override void PreConfigureServices(ServiceConfigurationContext context)
{
//动态Api-改进在pre中配置启动更快
PreConfigure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(YiAbpApplicationModule).Assembly,
options => options.RemoteServiceName = "default");
options.ConventionalControllers.Create(typeof(YiFrameworkRbacApplicationModule).Assembly,
options => options.RemoteServiceName = "rbac");
options.ConventionalControllers.Create(typeof(YiFrameworkBbsApplicationModule).Assembly,
options => options.RemoteServiceName = "bbs");
options.ConventionalControllers.Create(typeof(YiFrameworkChatHubApplicationModule).Assembly,
options => options.RemoteServiceName = "chat-hub");
options.ConventionalControllers.Create(
typeof(YiFrameworkTenantManagementApplicationModule).Assembly,
options => options.RemoteServiceName = "tenant-management");
options.ConventionalControllers.Create(typeof(YiFrameworkCodeGenApplicationModule).Assembly,
options => options.RemoteServiceName = "code-gen");
//统一前缀
options.ConventionalControllers.ConventionalControllerSettings.ForEach(x => x.RootPath = "api/app");
});
}
public override Task ConfigureServicesAsync(ServiceConfigurationContext context) public override Task ConfigureServicesAsync(ServiceConfigurationContext context)
{ {
var configuration = context.Services.GetConfiguration(); var configuration = context.Services.GetConfiguration();
@@ -94,27 +116,6 @@ namespace Yi.Abp.Web
//配置错误处理显示详情 //配置错误处理显示详情
Configure<AbpExceptionHandlingOptions>(options => { options.SendExceptionsDetailsToClients = true; }); Configure<AbpExceptionHandlingOptions>(options => { options.SendExceptionsDetailsToClients = true; });
//动态Api
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(YiAbpApplicationModule).Assembly,
options => options.RemoteServiceName = "default");
options.ConventionalControllers.Create(typeof(YiFrameworkRbacApplicationModule).Assembly,
options => options.RemoteServiceName = "rbac");
options.ConventionalControllers.Create(typeof(YiFrameworkBbsApplicationModule).Assembly,
options => options.RemoteServiceName = "bbs");
options.ConventionalControllers.Create(typeof(YiFrameworkChatHubApplicationModule).Assembly,
options => options.RemoteServiceName = "chat-hub");
options.ConventionalControllers.Create(
typeof(YiFrameworkTenantManagementApplicationModule).Assembly,
options => options.RemoteServiceName = "tenant-management");
options.ConventionalControllers.Create(typeof(YiFrameworkCodeGenApplicationModule).Assembly,
options => options.RemoteServiceName = "code-gen");
//统一前缀
options.ConventionalControllers.ConventionalControllerSettings.ForEach(x => x.RootPath = "api/app");
});
//【NewtonsoftJson严重问题逆天】设置api格式留给后人铭记 //【NewtonsoftJson严重问题逆天】设置api格式留给后人铭记
// service.AddControllers().AddNewtonsoftJson(options => // service.AddControllers().AddNewtonsoftJson(options =>
// { // {
@@ -182,7 +183,7 @@ namespace Yi.Abp.Web
//配置Hangfire定时任务存储开启redis后优先使用redis //配置Hangfire定时任务存储开启redis后优先使用redis
var redisConfiguration = configuration["Redis:Configuration"]; var redisConfiguration = configuration["Redis:Configuration"];
var redisEnabled = configuration["Redis:IsEnabled"]; var redisEnabled = configuration["Redis:IsEnabled"];
context.Services.AddHangfire(config => context.Services.AddHangfire(config=>
{ {
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled)) if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
{ {
@@ -257,11 +258,19 @@ namespace Yi.Abp.Web
{ {
OnMessageReceived = context => OnMessageReceived = context =>
{ {
//优先Query中获取再去cookies中获取
var accessToken = context.Request.Query["access_token"]; var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken)) if (!string.IsNullOrEmpty(accessToken))
{ {
context.Token = accessToken; context.Token = accessToken;
} }
else
{
if (context.Request.Cookies.TryGetValue("Token", out var cookiesToken))
{
context.Token = cookiesToken;
}
}
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -306,7 +315,6 @@ namespace Yi.Abp.Web
context.Services.AddAuthorization(); context.Services.AddAuthorization();
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -366,10 +374,11 @@ namespace Yi.Abp.Web
//日志记录 //日志记录
app.UseAbpSerilogEnrichers(); app.UseAbpSerilogEnrichers();
//Hangfire定时任务面板可配置授权 //Hangfire定时任务面板可配置授权意框架支持jwt
app.UseAbpHangfireDashboard("/hangfire", options => app.UseAbpHangfireDashboard("/hangfire",
options =>
{ {
// options.AsyncAuthorization = new[] { new AbpHangfireAuthorizationFilter() }; options.AsyncAuthorization = new[] { new YiTokenAuthorizationFilter(app.ApplicationServices) };
}); });
//终节点 //终节点

View File

@@ -20,6 +20,7 @@ namespace Yi.Abp.Tool.Commands
{ {
application.OnExecute(() => application.OnExecute(() =>
{ {
Console.WriteLine("正在克隆,请耐心等待");
StartCmd($"git clone {CloneAddress}"); StartCmd($"git clone {CloneAddress}");
return 0; return 0;
}); });

View File

@@ -36,6 +36,9 @@ namespace Yi.Abp.Tool.Commands
var soureOption = application.Option("-s|--soure", "模板来源gitee模板库分支名称: 默认值`default`", var soureOption = application.Option("-s|--soure", "模板来源gitee模板库分支名称: 默认值`default`",
CommandOptionType.SingleValue); CommandOptionType.SingleValue);
var dbmsOption = application.Option("-dbms|--dataBaseMs", "数据库类型,支持目前主流数据库",
CommandOptionType.SingleValue);
var moduleNameArgument = application.Argument("moduleName", "模块名", (_) => { }); var moduleNameArgument = application.Argument("moduleName", "模块名", (_) => { });
//子命令new list //子命令new list
@@ -58,6 +61,11 @@ namespace Yi.Abp.Tool.Commands
application.OnExecute(() => application.OnExecute(() =>
{ {
if (dbmsOption.HasValue())
{
Console.WriteLine($"检测到使用数据库类型-{dbmsOption.Value()}请在生成后只需在配置文件中更改DbConnOptions:Url及DbType即可支持目前主流数据库20+");
}
var path = string.Empty; var path = string.Empty;
if (pathOption.HasValue()) if (pathOption.HasValue())
{ {

View File

@@ -5,7 +5,7 @@
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Version>2.0.4</Version> <Version>2.0.5</Version>
<Authors>橙子老哥</Authors> <Authors>橙子老哥</Authors>
<Description>yi-framework框架配套工具</Description> <Description>yi-framework框架配套工具</Description>
<PackageProjectUrl>https://ccnetcore.com</PackageProjectUrl> <PackageProjectUrl>https://ccnetcore.com</PackageProjectUrl>

View File

@@ -41,12 +41,12 @@ const { width, height } = useWindowSize();
const WIDTH = 992; // refer to Bootstrap's responsive design const WIDTH = 992; // refer to Bootstrap's responsive design
watchEffect(() => { watchEffect(() => {
if (device.value === 'mobile' && sidebar.value.opened) { //if (device.value === 'mobile' && sidebar.value.opened) {
useAppStore().closeSideBar({ withoutAnimation: false }) // useAppStore().closeSideBar({ withoutAnimation: false })
} // }
if (width.value - 1 < WIDTH) { if (width.value - 1 < WIDTH) {
useAppStore().toggleDevice('mobile') useAppStore().toggleDevice('mobile')
useAppStore().closeSideBar({ withoutAnimation: true }) // useAppStore().closeSideBar({ withoutAnimation: true })
} else { } else {
useAppStore().toggleDevice('desktop') useAppStore().toggleDevice('desktop')
} }