Compare commits

..

1 Commits

Author SHA1 Message Date
橙子
eabbd55789 feat: 基础设施搭建 2024-10-14 00:29:55 +08:00
202 changed files with 12597 additions and 3542 deletions

1
.gitignore vendored
View File

@@ -269,7 +269,6 @@ dist
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Production.json
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Development.json
/Yi.Abp.Net8/test/Yi.Abp.Test/appsettings.Production.json
/Yi.Abp.Net8/tool/Yi.Abp.Tool.Web/appsettings.Development.json
database_backup
/Yi.Abp.Net8/src/Yi.Abp.Web/appsettings.Staging.json
/Yi.Abp.Net8/src/Yi.Abp.Web/logs/

View File

@@ -80,6 +80,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AuditLogging.S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Framework.AspNetCore.Authentication.OAuth", "framework\Yi.Framework.AspNetCore.Authentication.OAuth\Yi.Framework.AspNetCore.Authentication.OAuth.csproj", "{791AC2FA-50D3-4408-8D68-31DA72F608BE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{01300F0F-686E-47B3-821D-12424177867B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Web", "sample\Acme.BookStore.Web\Acme.BookStore.Web.csproj", "{576DBC97-4E5D-4444-B65C-F41649A5F8E0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain.Shared", "sample\Acme.BookStore.Domain.Shared\Acme.BookStore.Domain.Shared.csproj", "{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Domain", "sample\Acme.BookStore.Domain\Acme.BookStore.Domain.csproj", "{B615847F-8568-41D1-8B7E-63D61AE69F3D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application.Contracts", "sample\Acme.BookStore.Application.Contracts\Acme.BookStore.Application.Contracts.csproj", "{20827DB5-5CDE-491A-82E8-3CAB82618C1E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.Application", "sample\Acme.BookStore.Application\Acme.BookStore.Application.csproj", "{320273B6-7AE3-42DA-9675-D9AD4928A289}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Acme.BookStore.SqlSugarCore", "sample\Acme.BookStore.SqlSugarCore\Acme.BookStore.SqlSugarCore.csproj", "{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Test", "test\Yi.Abp.Test\Yi.Abp.Test.csproj", "{68627BC2-F049-4C69-AD17-81DF9478E8CE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tenant-management", "tenant-management", "{499A8C71-7892-42D0-A77E-48756E1EFF16}"
@@ -156,8 +170,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Yi.Abp.Tool.HttpApi.Client"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.SettingManagement.Application", "module\setting-management\Yi.Framework.SettingManagement.Application\Yi.Framework.SettingManagement.Application.csproj", "{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yi.Framework.BackgroundWorkers.Hangfire", "framework\Yi.Framework.BackgroundWorkers.Hangfire\Yi.Framework.BackgroundWorkers.Hangfire.csproj", "{862CA181-BEE6-4870-82D2-B662E527ED8C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -264,6 +276,30 @@ Global
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{791AC2FA-50D3-4408-8D68-31DA72F608BE}.Release|Any CPU.Build.0 = Release|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{576DBC97-4E5D-4444-B65C-F41649A5F8E0}.Release|Any CPU.Build.0 = Release|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D}.Release|Any CPU.Build.0 = Release|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B615847F-8568-41D1-8B7E-63D61AE69F3D}.Release|Any CPU.Build.0 = Release|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{20827DB5-5CDE-491A-82E8-3CAB82618C1E}.Release|Any CPU.Build.0 = Release|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Debug|Any CPU.Build.0 = Debug|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.ActiveCfg = Release|Any CPU
{320273B6-7AE3-42DA-9675-D9AD4928A289}.Release|Any CPU.Build.0 = Release|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7}.Release|Any CPU.Build.0 = Release|Any CPU
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68627BC2-F049-4C69-AD17-81DF9478E8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -392,10 +428,6 @@ Global
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956}.Release|Any CPU.Build.0 = Release|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{862CA181-BEE6-4870-82D2-B662E527ED8C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -429,6 +461,12 @@ Global
{73CCF2C4-B9FD-44AB-8D4B-0A421805B094} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{48806510-8E18-4E1E-9BAF-5B97E88C5FC3} = {73CCF2C4-B9FD-44AB-8D4B-0A421805B094}
{791AC2FA-50D3-4408-8D68-31DA72F608BE} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
{576DBC97-4E5D-4444-B65C-F41649A5F8E0} = {01300F0F-686E-47B3-821D-12424177867B}
{D7F8BD42-F6A2-4F0A-9212-391B5185A99D} = {01300F0F-686E-47B3-821D-12424177867B}
{B615847F-8568-41D1-8B7E-63D61AE69F3D} = {01300F0F-686E-47B3-821D-12424177867B}
{20827DB5-5CDE-491A-82E8-3CAB82618C1E} = {01300F0F-686E-47B3-821D-12424177867B}
{320273B6-7AE3-42DA-9675-D9AD4928A289} = {01300F0F-686E-47B3-821D-12424177867B}
{70CCBD89-C0A1-4AC8-9AFA-C86C356DFDD7} = {01300F0F-686E-47B3-821D-12424177867B}
{68627BC2-F049-4C69-AD17-81DF9478E8CE} = {0D10EEF2-FBAE-4C72-B816-A52823FC299B}
{499A8C71-7892-42D0-A77E-48756E1EFF16} = {2317227D-7796-4E7B-BEDB-7CD1CAE7B853}
{FA5BBAA1-08DC-472F-BB2C-5314E59D1556} = {499A8C71-7892-42D0-A77E-48756E1EFF16}
@@ -465,7 +503,6 @@ Global
{4AE84CDE-2A47-4D68-8E93-86193F72E4E8} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{C8F97775-D903-4365-A4FF-3DA97E318CD2} = {084CBEEC-5D37-4716-B9C7-D80D6960DFF4}
{2A31D7CB-BDCC-4253-BA73-273B6B5E1956} = {8C68059E-F3B1-4D28-A1C9-A5830F53E5D3}
{862CA181-BEE6-4870-82D2-B662E527ED8C} = {77B949E9-530E-45A5-9657-20F7D5C6875C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {23D6FBC9-C970-4641-BC1E-2AEA59F51C18}

View File

@@ -9,89 +9,84 @@ using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Options;
namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
{
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 mvcSettings =
mvcOptions.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
var serviceProvider = services.BuildServiceProvider();
var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>();
var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);
services.AddAbpSwaggerGen(
options =>
options =>
{
if (action is not null)
{
if (action is not null)
{
action.Invoke(options);
}
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
{
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{
options.SwaggerDoc(setting.RemoteServiceName,
new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
}
}
// 根据分组名称过滤 API 文档
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var settingOrNull = mvcSettings
.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly)
.FirstOrDefault();
if (settingOrNull is not null)
{
return docName == settingOrNull.RemoteServiceName;
}
}
return false;
});
options.CustomSchemaIds(type => type.FullName);
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (basePath is not null)
{
foreach (var item in Directory.GetFiles(basePath, "*.xml"))
{
options.IncludeXmlComments(item, true);
}
}
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
{
Description = "直接输入Token即可",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
[scheme] = new string[0]
});
options.OperationFilter<AddRequiredHeaderParameter>();
options.SchemaFilter<EnumSchemaFilter>();
action.Invoke(options);
}
);
// 配置分组,还需要去重,支持重写,如果外部传入后,将以外部为准
foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
{
if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
{
options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
}
}
// 根据分组名称过滤 API 文档
options.DocInclusionPredicate((docName, apiDesc) =>
{
if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
{
var settingOrNull = mvcSettings.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly).FirstOrDefault();
if (settingOrNull is not null)
{
return docName == settingOrNull.RemoteServiceName;
}
}
return false;
});
options.CustomSchemaIds(type => type.FullName);
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
if (basePath is not null)
{
foreach (var item in Directory.GetFiles(basePath, "*.xml"))
{
options.IncludeXmlComments(item, true);
}
}
options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
{
Description = "直接输入Token即可",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
Scheme = "bearer"
});
var scheme = new OpenApiSecurityScheme()
{
Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
};
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
[scheme] = new string[0]
});
options.OperationFilter<AddRequiredHeaderParameter>();
options.SchemaFilter<EnumSchemaFilter>();
}
);
return services;
}
@@ -108,6 +103,7 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
/// </summary>
/// <param name="model"></param>
/// <param name="context"></param>
public void Apply(OpenApiSchema model, SchemaFilterContext context)
{
if (context.Type.IsEnum)
@@ -116,7 +112,7 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
model.Type = "string";
model.Format = null;
StringBuilder stringBuilder = new StringBuilder();
Enum.GetNames(context.Type)
.ToList()
@@ -125,10 +121,9 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
Enum e = (Enum)Enum.Parse(context.Type, name);
var descrptionOrNull = GetEnumDescription(e);
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();
}
}
@@ -138,13 +133,13 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : null;
}
}
public class AddRequiredHeaderParameter : IOperationFilter
{
public static string HeaderKey { get; set; } = "__tenant";
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
@@ -155,8 +150,8 @@ namespace Yi.Framework.AspNetCore.Microsoft.Extensions.DependencyInjection
In = ParameterLocation.Header,
Required = false,
AllowEmptyValue = true,
Description = "租户id或者租户名称可空为默认租户"
Description="租户id或者租户名称可空为默认租户"
});
}
}
}
}

View File

@@ -1,37 +0,0 @@
using System.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Volo.Abp.AspNetCore.WebClientInfo;
namespace Yi.Framework.AspNetCore;
public class RealIpHttpContextWebClientInfoProvider : HttpContextWebClientInfoProvider
{
public RealIpHttpContextWebClientInfoProvider(ILogger<HttpContextWebClientInfoProvider> logger,
IHttpContextAccessor httpContextAccessor) : base(logger, httpContextAccessor)
{
}
protected override string? GetClientIpAddress()
{
try
{
var httpContext = HttpContextAccessor.HttpContext;
var headers = httpContext?.Request?.Headers;
if (headers != null && headers.ContainsKey("X-Forwarded-For"))
{
httpContext.Connection.RemoteIpAddress =
IPAddress.Parse(headers["X-Forwarded-For"].FirstOrDefault());
}
return httpContext?.Connection?.RemoteIpAddress?.ToString();
}
catch (Exception ex)
{
Logger.LogException(ex, LogLevel.Warning);
return null;
}
}
}

View File

@@ -11,7 +11,6 @@ using Newtonsoft.Json.Linq;
using Swashbuckle.AspNetCore.SwaggerGen;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.WebClientInfo;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Modularity;
using Yi.Framework.AspNetCore.Mvc;
@@ -23,11 +22,6 @@ namespace Yi.Framework.AspNetCore
)]
public class YiFrameworkAspNetCoreModule : AbpModule
{
public override void PostConfigureServices(ServiceConfigurationContext context)
{
var services = context.Services;
services.Replace(new ServiceDescriptor(typeof(IWebClientInfoProvider),
typeof(RealIpHttpContextWebClientInfoProvider), ServiceLifetime.Transient));
}
}
}

View File

@@ -1,45 +0,0 @@
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,17 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.BackgroundWorkers.Hangfire" Version="$(AbpVersion)" />
</ItemGroup>
</Project>

View File

@@ -1,35 +0,0 @@
using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddConventionalRegistrar(new YiHangfireConventionalRegistrar());
}
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
//定时任务自动注入Abp默认只有在Quartz才实现
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
foreach (var work in works)
{
//如果为空默认使用服务器本地utc时间
work.TimeZone ??= TimeZoneInfo.Local;
await backgroundWorkerManager.AddAsync(work);
}
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
var services = context.ServiceProvider;
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
}
}

View File

@@ -1,20 +0,0 @@
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.DependencyInjection;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class YiHangfireConventionalRegistrar : DefaultConventionalRegistrar
{
protected override bool IsConventionalRegistrationDisabled(Type type)
{
return !typeof(IHangfireBackgroundWorker).IsAssignableFrom(type) || base.IsConventionalRegistrationDisabled(type);
}
protected override List<Type> GetExposedServiceTypes(Type type)
{
return new List<Type>()
{
typeof(IHangfireBackgroundWorker)
};
}
}

View File

@@ -1,122 +0,0 @@
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,27 +0,0 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Yi.Framework.Core.Json;
public class DatetimeJsonConverter : JsonConverter<DateTime>
{
private string _format;
public DatetimeJsonConverter(string format="yyyy-MM-dd HH:mm:ss")
{
_format = format;
}
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType==JsonTokenType.String)
{
if (DateTime.TryParse(reader.GetString(), out DateTime dateTime)) return dateTime;
}
return reader.GetDateTime();
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}

View File

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

View File

@@ -0,0 +1,24 @@
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
{
/// <summary>
/// SqlSugarDb
/// </summary>
// IAbpLazyServiceProvider LazyServiceProvider { get; set; }
ISqlSugarClient SqlSugarClient { get; }
DbConnOptions Options { get; }
/// <summary>
/// 数据库备份
/// </summary>
void BackupDataBase();
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
}
}

View File

@@ -1,21 +0,0 @@
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

@@ -1,290 +0,0 @@
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

@@ -1,41 +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
{
/// <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

@@ -0,0 +1,123 @@
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,49 +1,362 @@
using System.Reflection;
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.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore;
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
namespace Yi.Framework.SqlSugarCore
{
protected IAbpLazyServiceProvider LazyServiceProvider { get; }
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
public class SqlSugarDbContext : ISqlSugarDbContext
{
this.LazyServiceProvider = lazyServiceProvider;
}
/// <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)
{
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;
}
protected ISqlSugarClient SqlSugarClient { get;private set; }
public int ExecutionOrder => 0;
public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
CustomDataFilter(sqlSugarClient);
}
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
}
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
}
/// <summary>
/// 上下文对象扩展
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void OnSqlSugarClientConfig(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);
}
CustomDataFilter(sqlSugarClient);
}
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
}
}
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{
}
}
public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
}
/// <summary>
/// 数据
/// </summary>
/// <param name="oldValue"></param>
/// <param name="entityInfo"></param>
protected virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
//审计日志
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
if (!DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
break;
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
//为空或者为默认最小值
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
if (CurrentUser.Id is not null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
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;
}
}
/// <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.IsOnlyIgnoreInsert = true;
// column.IsOnlyIgnoreUpdate = true;
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

@@ -1,290 +0,0 @@
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

@@ -0,0 +1,37 @@
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

@@ -17,4 +17,4 @@ namespace Yi.Framework.SqlSugarCore.Uow
DbContext = dbContext;
}
}
}
}

View File

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

View File

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

View File

@@ -16,7 +16,6 @@ public class AuditingStore : IAuditingStore, ITransientDependency
protected IUnitOfWorkManager UnitOfWorkManager { get; }
protected AbpAuditingOptions Options { get; }
protected IAuditLogInfoToAuditLogConverter Converter { get; }
public AuditingStore(
IAuditLogRepository auditLogRepository,
IUnitOfWorkManager unitOfWorkManager,
@@ -53,10 +52,10 @@ public class AuditingStore : IAuditingStore, ITransientDependency
protected virtual async Task SaveLogAsync(AuditLogInfo auditInfo)
{
Logger.LogDebug("Yi-请求追踪:" + JsonHelper.ObjToStr(auditInfo, "yyyy-MM-dd HH:mm:ss"));
using (var uow = UnitOfWorkManager.Begin())
using (var uow = UnitOfWorkManager.Begin(true))
{
await AuditLogRepository.InsertAsync(await Converter.ConvertAsync(auditInfo));
await uow.CompleteAsync();
}
}
}
}

View File

@@ -22,7 +22,6 @@ public class AuditLogActionEntity : Entity<Guid>, IMultiTenant
public virtual string? MethodName { get; protected set; }
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public virtual string? Parameters { get; protected set; }
public virtual DateTime? ExecutionTime { get; protected set; }

View File

@@ -103,14 +103,12 @@ namespace Yi.Framework.AuditLogging.Domain.Entities
public virtual string? CorrelationId { get; set; }
[SugarColumn(Length = 2000)]
public virtual string? BrowserInfo { get; protected set; }
public virtual string? HttpMethod { get; protected set; }
public virtual string? Url { get; protected set; }
[SugarColumn(ColumnDataType = StaticConfig.CodeFirst_BigString)]
public virtual string? Exceptions { get; protected set; }
public virtual string? Comments { get; protected set; }

View File

@@ -4,9 +4,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Etos;
namespace Yi.Framework.Bbs.Application.Extensions;
@@ -16,29 +14,6 @@ namespace Yi.Framework.Bbs.Application.Extensions;
/// 需考虑一致性问题,又不能上锁影响性能
/// </summary>
public class AccessLogMiddleware : IMiddleware, ITransientDependency
{
private static int _accessLogNumber = 0;
internal static void ResetAccessLogNumber()
{
_accessLogNumber = 0;
}
internal static int GetAccessLogNumber()
{
return _accessLogNumber;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
await next(context);
Interlocked.Increment(ref _accessLogNumber);
}
}
public class AccessLogResetEventHandler : ILocalEventHandler<AccessLogResetArgs>,
ITransientDependency
{
/// <summary>
/// 缓存前缀
@@ -64,28 +39,13 @@ public class AccessLogResetEventHandler : ILocalEventHandler<AccessLogResetArgs>
return redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled);
}
}
//该事件由job定时10秒触发
public async Task HandleEventAsync(AccessLogResetArgs eventData)
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
await next(context);
if (EnableRedisCache)
{
//分布式锁
if (await RedisClient.SetNxAsync("AccessLogLock",true,TimeSpan.FromSeconds(5)))
{
//自增长数
var incrNumber= AccessLogMiddleware.GetAccessLogNumber();
//立即重置,开始计算,方式丢失
AccessLogMiddleware.ResetAccessLogNumber();
if (incrNumber>0)
{
await RedisClient.IncrByAsync(
$"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date:yyyyMMdd}", incrNumber);
}
}
await RedisClient.IncrByAsync($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}", 1);
}
}
}

View File

@@ -1,42 +0,0 @@
using FreeRedis;
using Hangfire;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EventBus.Local;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Jobs;
public class AccessLogCacheJob : HangfireBackgroundWorkerBase
{
private readonly ILocalEventBus _localEventBus;
public AccessLogCacheJob(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
RecurringJobId = "访问日志写入缓存";
//每10秒执行一次将本地缓存转入redis防止丢数据
CronExpression = "*/10 * * * * *";
//
// JobDetail = JobBuilder.Create<AccessLogCacheJob>().WithIdentity(nameof(AccessLogCacheJob))
// .Build();
//每10秒执行一次将本地缓存转入redis防止丢数据
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogCacheJob))
// .WithSimpleSchedule((schedule) => { schedule.WithInterval(TimeSpan.FromSeconds(10)).RepeatForever();; })
// .Build();
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
{
await _localEventBus.PublishAsync(new AccessLogResetArgs());
}
}

View File

@@ -1,9 +1,11 @@
using FreeRedis;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Quartz;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Caches;
using Yi.Framework.Bbs.Domain.Shared.Enums;
@@ -11,7 +13,7 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Bbs.Application.Jobs;
public class AccessLogStoreJob : HangfireBackgroundWorkerBase
public class AccessLogStoreJob : QuartzBackgroundWorkerBase
{
private readonly ISqlSugarRepository<AccessLogAggregateRoot> _repository;
@@ -43,29 +45,24 @@ public class AccessLogStoreJob : HangfireBackgroundWorkerBase
public AccessLogStoreJob(ISqlSugarRepository<AccessLogAggregateRoot> repository)
{
_repository = repository;
JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
.Build();
RecurringJobId = "访问日志写入数据库";
//每分钟执行一次
CronExpression = "0 * * * * ?";
// JobDetail = JobBuilder.Create<AccessLogStoreJob>().WithIdentity(nameof(AccessLogStoreJob))
// .Build();
// //每分钟执行一次
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
// .WithCronSchedule("0 * * * * ?")
// .Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AccessLogStoreJob))
.WithCronSchedule("0 * * * * ?")
.Build();
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
public override async Task Execute(IJobExecutionContext context)
{
if (EnableRedisCache)
{
//当天的访问量
var number =
await RedisClient.GetAsync<long>($"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date:yyyyMMdd}");
await RedisClient.GetAsync<long>($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date}");
var entity = await _repository._DbQueryable.Where(x => x.AccessLogType == AccessLogTypeEnum.Request)
@@ -84,7 +81,7 @@ public class AccessLogStoreJob : HangfireBackgroundWorkerBase
}
//删除前一天的缓存
await RedisClient.DelAsync($"{CacheKeyPrefix}{AccessLogCacheConst.Key}:{DateTime.Now.Date.AddDays(-1):yyyyMMdd}");
await RedisClient.DelAsync($"{CacheKeyPrefix}:{AccessLogCacheConst.Key}:{DateTime.Now.Date.AddDays(-1)}");
}
}
}

View File

@@ -1,4 +1,5 @@
using Volo.Abp.BackgroundWorkers.Hangfire;
using Quartz;
using Volo.Abp.BackgroundWorkers.Quartz;
using Yi.Framework.Bbs.Domain.Managers;
namespace Yi.Framework.Bbs.Application.Jobs;
@@ -6,25 +7,20 @@ namespace Yi.Framework.Bbs.Application.Jobs;
/// <summary>
/// 每日任务job
/// </summary>
public class AssignmentExpireTimeOutJob : HangfireBackgroundWorkerBase
public class AssignmentExpireTimeOutJob : QuartzBackgroundWorkerBase
{
private readonly AssignmentManager _assignmentManager;
public AssignmentExpireTimeOutJob(AssignmentManager assignmentManager)
{
_assignmentManager = assignmentManager;
RecurringJobId = "每日任务系统超时检测";
//每分钟执行一次
CronExpression = "0 * * * * ?";
//
// JobDetail = JobBuilder.Create<AssignmentExpireTimeOutJob>().WithIdentity(nameof(AssignmentExpireTimeOutJob)).Build();
// //每个小时整点执行一次
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(AssignmentExpireTimeOutJob)).WithCronSchedule("0 0 * * * ?")
// .Build();
JobDetail = JobBuilder.Create<AssignmentExpireTimeOutJob>().WithIdentity(nameof(AssignmentExpireTimeOutJob)).Build();
//每个小时整点执行一次
Trigger = TriggerBuilder.Create().WithIdentity(nameof(AssignmentExpireTimeOutJob)).WithCronSchedule("0 0 * * * ?")
.Build();
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
public override async Task Execute(IJobExecutionContext context)
{
await _assignmentManager.ExpireTimeoutAsync();
}

View File

@@ -1,24 +1,20 @@
using Volo.Abp.BackgroundWorkers.Hangfire;
using Quartz;
using Volo.Abp.BackgroundWorkers.Quartz;
using Yi.Framework.Bbs.Domain.Managers;
namespace Yi.Framework.Bbs.Application.Jobs
{
public class InterestRecordsJob : HangfireBackgroundWorkerBase
public class InterestRecordsJob : QuartzBackgroundWorkerBase
{
private BankManager _bankManager;
public InterestRecordsJob(BankManager bankManager)
{
_bankManager = bankManager;
RecurringJobId = "银行利息积分刷新";
JobDetail = JobBuilder.Create<InterestRecordsJob>().WithIdentity(nameof(InterestRecordsJob)).Build();
//每个小时整点执行一次
CronExpression = "0 0 * * * ?";
// JobDetail = JobBuilder.Create<InterestRecordsJob>().WithIdentity(nameof(InterestRecordsJob)).Build();
//
// //每个小时整点执行一次
//
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob)).WithCronSchedule("0 0 * * * ?").Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob)).WithCronSchedule("0 0 * * * ?").Build();
//测试
// Trigger = TriggerBuilder.Create().WithIdentity(nameof(InterestRecordsJob))
@@ -27,8 +23,7 @@ namespace Yi.Framework.Bbs.Application.Jobs
// .RepeatForever())
//.Build();
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
public override async Task Execute(IJobExecutionContext context)
{
//创建一个记录,莫得了
await _bankManager.GetCurrentInterestRate();

View File

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

View File

@@ -27,19 +27,13 @@ namespace Yi.Framework.Bbs.Application.Services
var userEntity = await _bbsUserManager._userRepository.GetFirstAsync(x => x.UserName == userNameOrUserId);
if (userEntity == null)
{
throw new UserFriendlyException("该用户不存在");
throw new Volo.Abp.UserFriendlyException("该用户不存在");
}
userId= userEntity.Id;
}
var output =await _bbsUserManager.GetBbsUserInfoAsync(userId);
//不是自己
if (CurrentUser.Id != output.Id)
{
output.Phone = null;
output.Email=null;
}
return output!;
}
}

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ namespace Yi.Framework.Bbs.Application.Services.Integral
/// <summary>
/// 大转盘
/// Todo: 可放入领域层,但是太简单了,不重要
/// Todo: 可放入领域层
/// </summary>
/// <returns></returns>
[Authorize]
@@ -32,7 +32,13 @@ namespace Yi.Framework.Bbs.Application.Services.Integral
int[] values = new int[10] { 0, 10, 30, 50, 60, 80, 90, 100, 200, 666 };
var index = GetWheelIndex();
var value = values[index] - 50;
////不存在负数钱钱
//if (value < 0)
//{
// value = 0;
//}
//修改钱钱,如果钱钱不足,直接会丢出去,那本次抽奖将无效
await _localEventBus.PublishAsync(new MoneyChangeEventArgs { UserId = CurrentUser.Id!.Value, Number = value }, false);
@@ -41,7 +47,7 @@ namespace Yi.Framework.Bbs.Application.Services.Integral
private int GetWheelIndex()
{
int[] probabilities = {5 , 30, 40, 30, 20, 10, 4, 3, 2, 1 };
int[] probabilities = { 30, 40, 30, 15, 15, 10, 4, 3, 2, 1 };
int total = 0;
foreach (var prob in probabilities)

View File

@@ -1,9 +0,0 @@
namespace Yi.Framework.Bbs.Domain.Shared.Enums;
public enum GoodsTypeEnum
{
/// <summary>
/// 申请类型
/// </summary>
Apply
}

View File

@@ -19,10 +19,5 @@ namespace Yi.Framework.Bbs.Domain.Shared.Enums
/// 广播
/// </summary>
Broadcast,
/// <summary>
/// 钱钱
/// </summary>
Money
}
}

View File

@@ -1,6 +0,0 @@
namespace Yi.Framework.Bbs.Domain.Shared.Etos;
public class AccessLogResetArgs
{
}

View File

@@ -16,13 +16,6 @@ namespace Yi.Framework.Bbs.Domain.Shared.Etos
Message = message;
}
public BbsNoticeEventArgs( NoticeTypeEnum noticeType, Guid acceptUserId, string message)
{
NoticeType = noticeType;
AcceptUserId = acceptUserId;
Message = message;
}
/// <summary>
/// 发送广播
/// </summary>

View File

@@ -31,7 +31,7 @@ namespace Yi.Framework.Bbs.Domain.Entities.Forum
public override Guid Id { get; protected set; }
public bool IsDeleted { get; set; }
[SugarColumn(Length = 2000)]
[SugarColumn(Length = 500)]
public string Content { get; set; }
public Guid DiscussId { get; set; }

View File

@@ -1,73 +0,0 @@
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Enums;
namespace Yi.Framework.Bbs.Domain.Entities.Shop;
/// <summary>
/// 商品定义表
/// </summary>
[SugarTable("BbsGoods")]
public class BbsGoodsAggregateRoot: AggregateRoot<Guid>, IHasCreationTime
{
/// <summary>
/// 上架时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 商品类型
/// </summary>
public GoodsTypeEnum GoodsType{ get; set; }
/// <summary>
/// 下架时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
/// 商品名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 每人限购数量
/// </summary>
public int LimitNumber { get; set; }
/// <summary>
/// 当前库存数量
/// </summary>
public int StockNumber { get; set; }
/// <summary>
/// 商品图片url
/// </summary>
public string ImageUrl { get; set; }
/// <summary>
/// 描述
/// </summary>
public string Describe { get; set; }
/// <summary>
/// 编号
/// </summary>
public string Code { get; set; }
/// <summary>
/// 所需钱钱
/// </summary>
public decimal NeedMoney { get; set; }
/// <summary>
/// 所需价值
/// </summary>
public decimal NeedValue { get; set; }
/// <summary>
/// 所需积分
/// </summary>
public decimal NeedPoints { get; set; }
}

View File

@@ -1,32 +0,0 @@
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;
namespace Yi.Framework.Bbs.Domain.Entities.Shop;
/// <summary>
/// 商品申请记录表
/// </summary>
[SugarTable("BbsGoodsApply")]
public class BbsGoodsApplyAggregateRoot: AggregateRoot<Guid>, IHasCreationTime
{
/// <summary>
/// 商品id
/// </summary>
public Guid GoodsId { get; set; }
/// <summary>
/// 申请时间
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// 申请人用户id
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// 联系方式
/// </summary>
public string ContactInformation { get; set; }
}

View File

@@ -1,6 +0,0 @@
namespace Yi.Framework.Bbs.Domain.Entities.Shop.ValueObjects;
public class ShippingAddress
{
}

View File

@@ -25,40 +25,23 @@ namespace Yi.Framework.Bbs.Domain.EventHandlers
}
public async Task HandleEventAsync(BbsNoticeEventArgs eventData)
{
//是否需要离线存储
bool isStore = true;
var now = DateTime.Now;
switch (eventData.NoticeType)
//离线存储
var entity= await _repository.InsertReturnEntityAsync(new BbsNoticeAggregateRoot(eventData.NoticeType, eventData.Message, eventData.AcceptUserId));
switch (eventData.NoticeType)
{
case Shared.Enums.NoticeTypeEnum.Personal:
if (BbsNoticeHub.HubUserModels.TryGetValue(eventData.AcceptUserId.ToString(), out var hubUserModel))
{
_hubContext.Clients.Client(hubUserModel.ConnnectionId).SendAsync(NoticeTypeEnum.Personal.ToString(), eventData.Message,now);
_hubContext.Clients.Client(hubUserModel.ConnnectionId).SendAsync(NoticeTypeEnum.Personal.ToString(), eventData.Message,entity.CreationTime);
}
break;
case Shared.Enums.NoticeTypeEnum.Broadcast:
_hubContext.Clients.All.SendAsync(NoticeTypeEnum.Broadcast.ToString(), eventData.Message);
break;
case Shared.Enums.NoticeTypeEnum.Money:
if (BbsNoticeHub.HubUserModels.TryGetValue(eventData.AcceptUserId.ToString(), out var hubUserModel2))
{
_hubContext.Clients.Client(hubUserModel2.ConnnectionId).SendAsync(NoticeTypeEnum.Money.ToString(), eventData.Message,now);
}
isStore = false;
break;
default:
break;
}
if (isStore)
{ //离线存储
var entity= await _repository.InsertReturnEntityAsync(new BbsNoticeAggregateRoot(eventData.NoticeType, eventData.Message, eventData.AcceptUserId){CreationTime = now});
}
}
}
}

View File

@@ -1,10 +1,8 @@
using Volo.Abp;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
using Volo.Abp.EventBus.Local;
using Yi.Framework.Bbs.Domain.Entities;
using Yi.Framework.Bbs.Domain.Shared.Consts;
using Yi.Framework.Bbs.Domain.Shared.Enums;
using Yi.Framework.Bbs.Domain.Shared.Etos;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -13,11 +11,9 @@ namespace Yi.Framework.Bbs.Domain.EventHandlers
public class MoneyChangeEventHandler : ILocalEventHandler<MoneyChangeEventArgs>, ITransientDependency
{
private ISqlSugarRepository<BbsUserExtraInfoEntity> _userInfoRepository;
private ILocalEventBus _localEventBus;
public MoneyChangeEventHandler(ISqlSugarRepository<BbsUserExtraInfoEntity> userInfoRepository, ILocalEventBus localEventBus)
public MoneyChangeEventHandler(ISqlSugarRepository<BbsUserExtraInfoEntity> userInfoRepository)
{
_userInfoRepository = userInfoRepository;
_localEventBus = localEventBus;
}
public async Task HandleEventAsync(MoneyChangeEventArgs eventData)
{
@@ -32,9 +28,6 @@ namespace Yi.Framework.Bbs.Domain.EventHandlers
await _userInfoRepository._Db.Updateable<BbsUserExtraInfoEntity>()
.SetColumns(it => it.Money == it.Money + eventData.Number)
.Where(x => x.UserId == eventData.UserId).ExecuteCommandAsync();
await _localEventBus.PublishAsync(new BbsNoticeEventArgs(NoticeTypeEnum.Money, eventData.UserId,eventData.Number.ToString()), false);
}
}
}

View File

@@ -1,11 +0,0 @@
using Volo.Abp.Domain.Services;
namespace Yi.Framework.Bbs.Domain.Managers;
/// <summary>
/// bbs商品领域
/// </summary>
public class BbsShopManager: DomainService
{
}

View File

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

View File

@@ -78,12 +78,7 @@ namespace Yi.Framework.ChatHub.Domain.Managers
public async Task<ChatOnlineUserCacheItem?> GetUserAsync(Guid userId)
{
var key = new ChatOnlineUserCacheKey(CacheKeyPrefix);
var cacheUserOrNull= await RedisClient.HGetAsync(key.GetKey(), key.GetField(userId));
if (cacheUserOrNull is null)
{
return null;
}
var cacheUser = System.Text.Json.JsonSerializer.Deserialize<ChatOnlineUserCacheItem>(cacheUserOrNull);
var cacheUser = System.Text.Json.JsonSerializer.Deserialize<ChatOnlineUserCacheItem>(await RedisClient.HGetAsync(key.GetKey(), key.GetField(userId)));
return cacheUser;
}
}

View File

@@ -4,7 +4,5 @@
{
public Guid Uuid { get; set; } = Guid.Empty;
public byte[] Img { get; set; }
public bool IsEnableCaptcha { get; set; }
}
}

View File

@@ -3,9 +3,5 @@
public class PhoneCaptchaImageDto
{
public string Phone { get; set; }
public string Uuid { get; set; }
public string Code { get; set; }
}
}

View File

@@ -5,7 +5,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
/// </summary>
public class DictionaryCreateInputVo
{
public int OrderNum { get; set; }
public string? Remark { get; set; }
public string? ListClass { get; set; }
public string? CssClass { get; set; }

View File

@@ -4,7 +4,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
{
public class DictionaryGetListOutputDto : EntityDto<Guid>
{
public int OrderNum { get; set; }
public DateTime CreationTime { get; set; } = DateTime.Now;
public Guid? CreatorId { get; set; }
public string? Remark { get; set; }

View File

@@ -4,7 +4,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
{
public class DictionaryGetOutputDto : EntityDto<Guid>
{
public int OrderNum { get; set; }
public DateTime CreationTime { get; set; } = DateTime.Now;
public Guid? CreatorId { get; set; }
public string? Remark { get; set; }

View File

@@ -2,7 +2,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary
{
public class DictionaryUpdateInputVo
{
public int OrderNum { get; set; }
public string? Remark { get; set; }
public string? ListClass { get; set; }
public string? CssClass { get; set; }

View File

@@ -20,7 +20,7 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
public string? Remark { get; set; }
public string? Component { get; set; }
public string? Query { get; set; }
public string? RouterName { get; set; }
public int OrderNum { get; set; }
//public List<MenuEntity>? Children { get; set; }
}

View File

@@ -24,8 +24,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Menu
public int OrderNum { get; set; }
public string? RouterName { get; set; }
//public List<MenuEntity>? Children { get; set; }
}
}

View File

@@ -3,7 +3,6 @@ namespace Yi.Framework.Rbac.Application.Contracts.Dtos.Post
public class PostUpdateInputVo
{
public bool? State { get; set; }
public int OrderNum { get; set; }
public string PostCode { get; set; }
public string PostName { get; set; }
public string? Remark { get; set; }

View File

@@ -6,14 +6,16 @@ using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Quartz;
using Quartz.Logging;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.Domain.Repositories;
using Yi.Framework.Rbac.Domain.Shared.Options;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.Rbac.Application.Jobs
{
public class BackupDataBaseJob: HangfireBackgroundWorkerBase
public class BackupDataBaseJob : QuartzBackgroundWorkerBase
{
private ISqlSugarDbContext _dbContext;
private IOptions<RbacOptions> _options;
@@ -22,12 +24,13 @@ namespace Yi.Framework.Rbac.Application.Jobs
_options = options;
_dbContext = dbContext;
RecurringJobId = "数据库备份";
JobDetail = JobBuilder.Create<BackupDataBaseJob>().WithIdentity(nameof(BackupDataBaseJob)).Build();
//每天00点与24点进行备份
CronExpression = "0 0 0,12 * * ? ";
Trigger = TriggerBuilder.Create().WithIdentity(nameof(BackupDataBaseJob)).WithCronSchedule("0 0 0,12 * * ? ").Build();
//Trigger = TriggerBuilder.Create().WithIdentity(nameof(BackupDataBaseJob)).WithSimpleSchedule(x=>x.WithIntervalInSeconds(10)).Build();
}
public override Task DoWorkAsync(CancellationToken cancellationToken = new CancellationToken())
public override Task Execute(IJobExecutionContext context)
{
if (_options.Value.EnableDataBaseBackup)
{

View File

@@ -82,12 +82,12 @@ namespace Yi.Framework.Rbac.Application.Services
/// 校验图片登录验证码,无需和账号绑定
/// </summary>
[AllowAnonymous]
private void ValidationImageCaptcha(string? uuid,string? code )
private void ValidationImageCaptcha(LoginInputVo input)
{
if (_rbacOptions.EnableCaptcha)
{
//登录不想要验证码 ,可不校验
if (!_captcha.Validate(uuid, code))
if (!_captcha.Validate(input.Uuid, input.Code))
{
throw new UserFriendlyException("验证码错误");
}
@@ -109,7 +109,7 @@ namespace Yi.Framework.Rbac.Application.Services
}
//校验验证码
ValidationImageCaptcha(input.Uuid,input.Code);
ValidationImageCaptcha(input);
UserAggregateRoot user = new();
//校验
@@ -157,8 +157,7 @@ namespace Yi.Framework.Rbac.Application.Services
{
var uuid = _guidGenerator.Create();
var captcha = _captcha.Generate(uuid.ToString());
var enableCaptcha = _rbacOptions.EnableCaptcha;
return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid,IsEnableCaptcha= enableCaptcha };
return new CaptchaImageDto { Img = captcha.Bytes, Uuid = uuid };
}
/// <summary>
@@ -199,15 +198,12 @@ namespace Yi.Framework.Rbac.Application.Services
}
/// <summary>
/// 手机验证码-需通过图形验证码
/// 手机验证码
/// </summary>
/// <returns></returns>
private async Task<object> PostCaptchaPhoneAsync(ValidationPhoneTypeEnum validationPhoneType,
PhoneCaptchaImageDto input)
{
//验证uuid 和 验证码
ValidationImageCaptcha(input.Uuid,input.Code);
await ValidationPhone(input.Phone);
//注册的手机号验证,是不能已经注册过的
@@ -355,13 +351,13 @@ namespace Yi.Framework.Rbac.Application.Services
{
//将后端菜单转换成前端路由,组件级别需要过滤
output =
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Ruoyi).ToList()).Vue3RuoYiRouterBuild();
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3RuoYiRouterBuild();
}
else if (routerType == "pure")
{
//将后端菜单转换成前端路由,组件级别需要过滤
output =
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus.Where(x=>x.MenuSource==MenuSourceEnum.Pure).ToList()).Vue3PureRouterBuild();
ObjectMapper.Map<List<MenuDto>, List<MenuAggregateRoot>>(menus).Vue3PureRouterBuild();
}
return output;

View File

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

View File

@@ -6,6 +6,7 @@ using Yi.Framework.Ddd.Application;
using Yi.Framework.Rbac.Application.Contracts.Dtos.Dictionary;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.Rbac.Domain.Shared.Consts;
using Yi.Framework.SqlSugarCore.Abstractions;
@@ -14,27 +15,29 @@ namespace Yi.Framework.Rbac.Application.Services
/// <summary>
/// Dictionary服务实现
/// </summary>
public class DictionaryService : YiCrudAppService<DictionaryEntity, DictionaryGetOutputDto, DictionaryGetListOutputDto, Guid, DictionaryGetListInputVo, DictionaryCreateInputVo, DictionaryUpdateInputVo>,
IDictionaryService
public class DictionaryService : YiCrudAppService<DictionaryEntity, DictionaryGetOutputDto,
DictionaryGetListOutputDto, Guid, DictionaryGetListInputVo, DictionaryCreateInputVo,
DictionaryUpdateInputVo>,
IDictionaryService
{
private ISqlSugarRepository<DictionaryEntity, Guid> _repository;
public DictionaryService(ISqlSugarRepository<DictionaryEntity, Guid> repository) : base(repository)
{
_repository= repository;
_repository = repository;
}
/// <summary>
/// 查询
/// </summary>
public override async Task<PagedResultDto<DictionaryGetListOutputDto>> GetListAsync(DictionaryGetListInputVo input)
public override async Task<PagedResultDto<DictionaryGetListOutputDto>> GetListAsync(
DictionaryGetListInputVo input)
{
RefAsync<int> total = 0;
var entities = await _repository._DbQueryable
.WhereIF(input.DictType is not null, x => x.DictType == input.DictType)
.WhereIF(input.DictLabel is not null, x => x.DictLabel!.Contains(input.DictLabel!))
.WhereIF(input.State is not null, x => x.State == input.State)
.OrderByDescending(x => x.OrderNum)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<DictionaryGetListOutputDto>
{
@@ -57,4 +60,4 @@ namespace Yi.Framework.Rbac.Application.Services
return result;
}
}
}
}

View File

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

View File

@@ -0,0 +1,220 @@
using System.Reflection;
using Mapster;
using Microsoft.AspNetCore.Mvc;
using Quartz;
using Quartz.Impl.Matchers;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Timing;
using Yi.Framework.Rbac.Application.Contracts.Dtos.Task;
using Yi.Framework.Rbac.Application.Contracts.IServices;
using Yi.Framework.Rbac.Domain.Shared.Enums;
namespace Yi.Framework.Rbac.Application.Services.Monitor
{
public class TaskService : ApplicationService, ITaskService
{
private readonly ISchedulerFactory _schedulerFactory;
private readonly IClock _clock;
public TaskService(ISchedulerFactory schedulerFactory, IClock clock)
{
_clock = clock;
_schedulerFactory = schedulerFactory;
}
/// <summary>
/// 单查job
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
[HttpGet("task/{jobId}")]
public async Task<TaskGetOutput> GetAsync([FromRoute] string jobId)
{
var scheduler = await _schedulerFactory.GetScheduler();
var jobDetail = await scheduler.GetJobDetail(new JobKey(jobId));
var trigger = (await scheduler.GetTriggersOfJob(new JobKey(jobId))).First();
//状态
var state = await scheduler.GetTriggerState(trigger.Key);
var output = new TaskGetOutput
{
JobId = jobDetail.Key.Name,
GroupName = jobDetail.Key.Group,
JobType = jobDetail.JobType.Name,
Properties = Newtonsoft.Json.JsonConvert.SerializeObject(jobDetail.JobDataMap),
Concurrent = !jobDetail.ConcurrentExecutionDisallowed,
Description = jobDetail.Description,
LastRunTime = _clock.Normalize(trigger.GetPreviousFireTimeUtc()?.DateTime ?? DateTime.MinValue),
NextRunTime = _clock.Normalize(trigger.GetNextFireTimeUtc()?.DateTime ?? DateTime.MinValue),
AssemblyName = jobDetail.JobType.Assembly.GetName().Name,
Status = state.ToString()
};
if (trigger is ISimpleTrigger simple)
{
output.TriggerArgs = Math.Round(simple.RepeatInterval.TotalMinutes, 2).ToString() + "分钟";
output.Type = JobTypeEnum.Millisecond;
output.Millisecond = simple.RepeatInterval.TotalMilliseconds;
}
else if (trigger is ICronTrigger cron)
{
output.TriggerArgs = cron.CronExpressionString!;
output.Type = JobTypeEnum.Cron;
output.Cron = cron.CronExpressionString;
}
return output;
}
/// <summary>
/// 多查job
/// </summary>
/// <returns></returns>
public async Task<PagedResultDto<TaskGetListOutput>> GetListAsync([FromQuery] TaskGetListInput input)
{
var items = new List<TaskGetOutput>();
var scheduler = await _schedulerFactory.GetScheduler();
var groups = await scheduler.GetJobGroupNames();
foreach (var groupName in groups)
{
foreach (var jobKey in await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)))
{
string jobName = jobKey.Name;
string jobGroup = jobKey.Group;
var triggers = (await scheduler.GetTriggersOfJob(jobKey)).First();
items.Add(await GetAsync(jobName));
}
}
var output = items.Skip((input.SkipCount - 1) * input.MaxResultCount).Take(input.MaxResultCount)
.OrderByDescending(x => x.LastRunTime)
.ToList();
return new PagedResultDto<TaskGetListOutput>(items.Count(), output.Adapt<List<TaskGetListOutput>>());
}
/// <summary>
/// 创建job
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task CreateAsync(TaskCreateInput input)
{
var scheduler = await _schedulerFactory.GetScheduler();
//设置启动时执行一次,然后最大只执行一次
//jobBuilder
var jobClassType = Assembly.Load(input.AssemblyName).GetTypes().Where(x => x.Name == input.JobType).FirstOrDefault();
if (jobClassType is null)
{
throw new UserFriendlyException($"程序集:{input.AssemblyName}{input.JobType} 不存在");
}
var jobBuilder = JobBuilder.Create(jobClassType).WithIdentity(new JobKey(input.JobId, input.GroupName))
.WithDescription(input.Description);
if (!input.Concurrent)
{
jobBuilder.DisallowConcurrentExecution();
}
//triggerBuilder
TriggerBuilder triggerBuilder = null;
switch (input.Type)
{
case JobTypeEnum.Cron:
triggerBuilder =
TriggerBuilder.Create()
.WithCronSchedule(input.Cron);
break;
case JobTypeEnum.Millisecond:
triggerBuilder =
TriggerBuilder.Create().StartNow()
.WithSimpleSchedule(x => x
.WithInterval(TimeSpan.FromMilliseconds(input.Millisecond ?? 10000))
.RepeatForever()
);
break;
}
//作业计划,单个jobBuilder与多个triggerBuilder组合
await scheduler.ScheduleJob(jobBuilder.Build(), triggerBuilder.Build());
}
/// <summary>
/// 移除job
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task DeleteAsync(IEnumerable<string> id)
{
var scheduler = await _schedulerFactory.GetScheduler();
await scheduler.DeleteJobs(id.Select(x => new JobKey(x)).ToList());
}
/// <summary>
/// 暂停job
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
[HttpPut]
public async Task PauseAsync(string jobId)
{
var scheduler = await _schedulerFactory.GetScheduler();
await scheduler.PauseJob(new JobKey(jobId));
}
/// <summary>
/// 开始job
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
[HttpPut]
public async Task StartAsync(string jobId)
{
var scheduler = await _schedulerFactory.GetScheduler();
await scheduler.ResumeJob(new JobKey(jobId));
}
/// <summary>
/// 更新job
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
public async Task UpdateAsync(string id, TaskUpdateInput input)
{
await DeleteAsync(new List<string>() { id });
await CreateAsync(input.Adapt<TaskCreateInput>());
}
[HttpPost("task/run-once/{id}")]
public async Task RunOnceAsync([FromRoute] string id)
{
var scheduler = await _schedulerFactory.GetScheduler();
var jobDetail = await scheduler.GetJobDetail(new JobKey(id));
var jobBuilder = JobBuilder.Create(jobDetail.JobType).WithIdentity(new JobKey(Guid.NewGuid().ToString()));
//设置启动时执行一次,然后最大只执行一次
var trigger = TriggerBuilder.Create().WithIdentity(Guid.NewGuid().ToString()).StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInHours(1)
.WithRepeatCount(1))
.Build();
await scheduler.ScheduleJob(jobBuilder.Build(), trigger);
}
}
}

View File

@@ -20,12 +20,12 @@ namespace Yi.Framework.Rbac.Application.Services.RecordLog
public override async Task<PagedResultDto<LoginLogGetListOutputDto>> GetListAsync(LoginLogGetListInputVo input)
{
RefAsync<int> total = 0;
//if (input.Sorting.IsNullOrWhiteSpace())
// input.Sorting = $"{nameof(LoginLogAggregateRoot.CreationTime)} Desc";
if (input.Sorting.IsNullOrWhiteSpace())
input.Sorting = $"{nameof(LoginLogAggregateRoot.CreationTime)} Desc";
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.LoginIp), x => x.LoginIp.Contains(input.LoginIp!))
.WhereIF(!string.IsNullOrEmpty(input.LoginUser), x => x.LoginUser!.Contains(input.LoginUser!))
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
.OrderByDescending(it => it.CreationTime) //降序
.OrderBy(input.Sorting)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<LoginLogGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
}

View File

@@ -24,12 +24,12 @@ namespace Yi.Framework.Rbac.Application.Services.RecordLog
public override async Task<PagedResultDto<OperationLogGetListOutputDto>> GetListAsync(OperationLogGetListInputVo input)
{
RefAsync<int> total = 0;
//if (input.Sorting.IsNullOrWhiteSpace())
// input.Sorting = $"{nameof(OperationLogEntity.CreationTime)} Desc";
if (input.Sorting.IsNullOrWhiteSpace())
input.Sorting = $"{nameof(OperationLogEntity.CreationTime)} Desc";
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.OperUser), x => x.OperUser.Contains(input.OperUser!))
.WhereIF(input.OperType is not null, x => x.OperType == input.OperType)
.WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
.OrderByDescending(it => it.CreationTime) //降序
.OrderBy(input.Sorting)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<OperationLogGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
}

View File

@@ -30,7 +30,6 @@ namespace Yi.Framework.Rbac.Application.Services.System
var entities = await _repository._DbQueryable.WhereIF(!string.IsNullOrEmpty(input.PostName),
x => x.PostName.Contains(input.PostName!))
.WhereIF(input.State is not null, x => x.State == input.State)
.OrderByDescending(x => x.OrderNum)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<PostGetListOutputDto>(total, await MapToGetListOutputDtosAsync(entities));
}

View File

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

View File

@@ -10,15 +10,13 @@
<ItemGroup>
<PackageReference Include="Lazy.Captcha.Core" Version="2.0.7" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="2.88.7" />
<PackageReference Include="Volo.Abp.BackgroundJobs.Hangfire" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.BackgroundWorkers.Quartz" Version="$(AbpVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\Yi.Framework.Ddd.Application\Yi.Framework.Ddd.Application.csproj" />
<ProjectReference Include="..\Yi.Framework.Rbac.Application.Contracts\Yi.Framework.Rbac.Application.Contracts.csproj" />
<ProjectReference Include="..\Yi.Framework.Rbac.Domain\Yi.Framework.Rbac.Domain.csproj" />
<ProjectReference Include="..\..\..\framework\Yi.Framework.BackgroundWorkers.Hangfire\Yi.Framework.BackgroundWorkers.Hangfire.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,8 +1,7 @@
using Lazy.Captcha.Core.Generator;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.Modularity;
using Yi.Framework.Ddd.Application;
using Yi.Framework.Rbac.Application.Contracts;
@@ -16,7 +15,8 @@ namespace Yi.Framework.Rbac.Application
typeof(YiFrameworkRbacDomainModule),
typeof(YiFrameworkDddApplicationModule)
typeof(YiFrameworkDddApplicationModule),
typeof(AbpBackgroundWorkersQuartzModule)
)]
public class YiFrameworkRbacApplicationModule : AbpModule
{
@@ -24,10 +24,7 @@ namespace Yi.Framework.Rbac.Application
{
var service = context.Services;
service.AddCaptcha(options =>
{
options.CaptchaType = CaptchaType.ARITHMETIC;
});
service.AddCaptcha();
}
public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)

View File

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

View File

@@ -63,12 +63,12 @@ namespace Yi.Framework.Rbac.Domain.Entities
/// <summary>
/// 部门名称
///</summary>
public string DeptName { get; set; }
public string DeptName { get; set; } = string.Empty;
/// <summary>
/// 部门编码
///</summary>
[SugarColumn(ColumnName = "DeptCode")]
public string DeptCode { get; set; }
public string DeptCode { get; set; } = string.Empty;
/// <summary>
/// 负责人
///</summary>

View File

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

View File

@@ -41,7 +41,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot shenzhenDept = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "SZ",
DeptName = "深圳总公司",
OrderNum = 100,
IsDeleted = false,
@@ -52,7 +52,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot jiangxiDept = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "JX",
DeptName = "江西总公司",
OrderNum = 100,
IsDeleted = false,
@@ -64,7 +64,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot szDept1 = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "YF",
DeptName = "研发部门",
OrderNum = 100,
IsDeleted = false,
@@ -74,7 +74,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot szDept2 = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "SC",
DeptName = "市场部门",
OrderNum = 100,
IsDeleted = false,
@@ -84,7 +84,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot szDept3 = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "CS",
DeptName = "测试部门",
OrderNum = 100,
IsDeleted = false,
@@ -94,7 +94,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot szDept4 = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "CW",
DeptName = "财务部门",
OrderNum = 100,
IsDeleted = false,
@@ -104,7 +104,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot szDept5 = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "YW",
DeptName = "运维部门",
OrderNum = 100,
IsDeleted = false,
@@ -115,7 +115,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot jxDept1 = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "SC",
DeptName = "市场部门",
OrderNum = 100,
IsDeleted = false,
@@ -126,7 +126,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
DeptAggregateRoot jxDept2 = new DeptAggregateRoot(_guidGenerator.Create())
{
DeptCode = "CW2",
DeptName = "财务部门",
OrderNum = 100,
IsDeleted = false,

View File

@@ -19,7 +19,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
public async Task SeedAsync(DataSeedContext context)
{
if (!await _repository.IsAnyAsync(x => x.MenuName == "系统管理" && x.MenuSource == MenuSourceEnum.Pure))
if (!await _repository.IsAnyAsync(x => x.MenuName == "系统管理"&&x.MenuSource==MenuSourceEnum.Pure))
{
await _repository.InsertManyAsync(GetSeedData());
}
@@ -37,7 +37,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
OrderNum = 100
};
entities.Add(system);
//系统监控
MenuAggregateRoot monitoring = new MenuAggregateRoot(_guidGenerator.Create())
{
@@ -64,7 +64,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
};
entities.Add(online);
//Yi框架
MenuAggregateRoot guide = new MenuAggregateRoot(_guidGenerator.Create())
{
@@ -77,7 +77,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
Component = null
};
entities.Add(guide);
//用户管理
MenuAggregateRoot user = new MenuAggregateRoot(_guidGenerator.Create())
{
@@ -141,7 +141,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
entities.Add(userRemove);
MenuAggregateRoot userResetPwd = new MenuAggregateRoot(_guidGenerator.Create())
MenuAggregateRoot userResetPwd = new MenuAggregateRoot(_guidGenerator.Create())
{
MenuName = "重置密码",
@@ -408,7 +408,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
IsDeleted = false
};
entities.Add(postRemove);
//操作日志
MenuAggregateRoot operationLog = new MenuAggregateRoot(_guidGenerator.Create())
@@ -491,78 +491,10 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
OrderNum = 100,
ParentId = loginLog.Id,
IsDeleted = false,
};
entities.Add(loginLogRemove);
//参数设置
MenuAggregateRoot config = new MenuAggregateRoot(_guidGenerator.Create())
{
MenuName = "参数设置",
PermissionCode = "system:config:list",
MenuType = MenuTypeEnum.Menu,
Router = "config",
IsShow = true,
IsLink = false,
IsCache = true,
Component = "/system/config/index",
MenuIcon = "ri:edit-box-line",
OrderNum = 94,
ParentId = system.Id,
IsDeleted = false
};
entities.Add(config);
MenuAggregateRoot configQuery = new MenuAggregateRoot(_guidGenerator.Create())
{
MenuName = "参数查询",
PermissionCode = "system:config:query",
MenuType = MenuTypeEnum.Component,
OrderNum = 100,
ParentId = config.Id,
IsDeleted = false
};
entities.Add(configQuery);
MenuAggregateRoot configAdd = new MenuAggregateRoot(_guidGenerator.Create())
{
MenuName = "参数新增",
PermissionCode = "system:config:add",
MenuType = MenuTypeEnum.Component,
OrderNum = 100,
ParentId = config.Id,
IsDeleted = false
};
entities.Add(configAdd);
MenuAggregateRoot configEdit = new MenuAggregateRoot(_guidGenerator.Create())
{
MenuName = "参数修改",
PermissionCode = "system:config:edit",
MenuType = MenuTypeEnum.Component,
OrderNum = 100,
ParentId = config.Id,
IsDeleted = false
};
entities.Add(configEdit);
MenuAggregateRoot configRemove = new MenuAggregateRoot(_guidGenerator.Create())
{
MenuName = "参数删除",
PermissionCode = "system:config:remove",
MenuType = MenuTypeEnum.Component,
OrderNum = 100,
ParentId = config.Id,
IsDeleted = false
};
entities.Add(configRemove);
//默认值
entities.ForEach(m =>
{

View File

@@ -227,22 +227,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore.DataSeeds
IsDeleted = false,
};
entities.Add(swagger);
//表单构建
MenuAggregateRoot builder = new MenuAggregateRoot(_guidGenerator.Create(), tool.Id)
{
MenuName = "表单生成器",
MenuType = MenuTypeEnum.Menu,
Router = "build",
IsShow = true,
IsLink = false,
MenuIcon = "form",
Component = "tool/build/index",
IsCache = true,
OrderNum = 101,
IsDeleted = false,
};
entities.Add(builder);
// //ERP
// MenuAggregateRoot erp = new MenuAggregateRoot(_guidGenerator.Create())

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.TenantManagement.Domain.Shared
{
public class TenantConst
{
public static string TenantDbDefaultName = "Master";
}
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.TenantManagement.Domain.Shared" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,11 @@
using Volo.Abp.Modularity;
using Volo.Abp.TenantManagement;
namespace YiFrameworkTenantManagementDomain.Shared
{
[DependsOn(typeof(AbpTenantManagementDomainSharedModule))]
public class YiFrameworkTenantManagementDomainSharedModule : AbpModule
{
}
}

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<ProjectReference Include="..\..\framework\Yi.Framework.Ddd.Application.Contracts\Yi.Framework.Ddd.Application.Contracts.csproj" />
<ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.Application.Contracts\Yi.Framework.Bbs.Application.Contracts.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.Application.Contracts\Yi.Framework.Rbac.Application.Contracts.csproj" />
<ProjectReference Include="..\Acme.BookStore.Domain.Shared\Acme.BookStore.Domain.Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using System.ComponentModel.DataAnnotations;
using Acme.BookStore.Domain.Shared.Enums;
namespace Acme.BookStore.Application.Contracts.Dtos.Book
{
public class BookCreateUpdateDto
{
[Required]
[StringLength(128)]
public string Name { get; set; }
[Required]
public BookTypeEnum Type { get; set; } = BookTypeEnum.Undefined;
[Required]
[DataType(DataType.Date)]
public DateTime PublishDate { get; set; } = DateTime.Now;
[Required]
public float Price { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using Acme.BookStore.Domain.Shared.Enums;
using Volo.Abp.Application.Dtos;
namespace Acme.BookStore.Application.Contracts.Dtos.Book
{
public class BookDto : AuditedEntityDto<Guid>
{
public string Name { get; set; }
public BookTypeEnum Type { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using Acme.BookStore.Application.Contracts.Dtos.Book;
using Volo.Abp.Application.Dtos;
using Yi.Framework.Ddd.Application.Contracts;
namespace Acme.BookStore.Application.Contracts.IServices
{
public interface IBookAppService :
IYiCrudAppService< //Defines CRUD methods
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
BookCreateUpdateDto> //Used to create/update a book
{
}
}

View File

@@ -0,0 +1,20 @@
using Volo.Abp.Modularity;
using Acme.BookStore.Domain.Shared;
using Yi.Framework.Bbs.Application.Contracts;
using Yi.Framework.Ddd.Application.Contracts;
using Yi.Framework.Rbac.Application.Contracts;
namespace Acme.BookStore.Application.Contracts
{
[DependsOn(
typeof(YiAbpDomainSharedModule),
typeof(YiFrameworkRbacApplicationContractsModule),
typeof(YiFrameworkBbsApplicationContractsModule),
typeof(YiFrameworkDddApplicationContractsModule))]
public class YiAbpApplicationContractsModule:AbpModule
{
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<ProjectReference Include="..\..\framework\Yi.Framework.Ddd.Application\Yi.Framework.Ddd.Application.csproj" />
<ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.Application\Yi.Framework.Bbs.Application.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.Application\Yi.Framework.Rbac.Application.csproj" />
<ProjectReference Include="..\Acme.BookStore.Application.Contracts\Acme.BookStore.Application.Contracts.csproj" />
<ProjectReference Include="..\Acme.BookStore.Domain\Acme.BookStore.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Jobs\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,36 @@
using Quartz;
using SqlSugar;
using Volo.Abp.BackgroundWorkers.Quartz;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Uow;
using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Acme.BookStore.Application.Jobs
{
/// <summary>
/// 定时任务
/// </summary>
public class TestJob : QuartzBackgroundWorkerBase
{
private ISqlSugarRepository<UserAggregateRoot> _repository;
public TestJob(ISqlSugarRepository<UserAggregateRoot> repository)
{
_repository = repository;
JobDetail = JobBuilder.Create<TestJob>().WithIdentity(nameof(TestJob)).Build();
Trigger = TriggerBuilder.Create().WithIdentity(nameof(TestJob)).StartNow()
.WithSimpleSchedule(x => x
.WithIntervalInSeconds(1000 * 60)
.RepeatForever())
.Build();
}
public override async Task Execute(IJobExecutionContext context)
{
//定时任务,非常简单
Console.WriteLine("你好,世界");
// var eneities= await _repository.GetListAsync();
//var entities= await _sqlSugarClient.Queryable<UserEntity>().ToListAsync();
//await Console.Out.WriteLineAsync(entities.Count().ToString());
}
}
}

View File

@@ -0,0 +1,42 @@
using Acme.BookStore.Application.Contracts.Dtos.Book;
using Acme.BookStore.Application.Contracts.IServices;
using Acme.BookStore.Domain.Entities;
using SqlSugar;
using Volo.Abp.Application.Dtos;
using Yi.Framework.Ddd.Application;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Acme.BookStore.Application.Services
{
public class BookAppService :
YiCrudAppService<
BookAggregateRoot, //The Book entity
BookDto, //Used to show books
Guid, //Primary key of the book entity
PagedAndSortedResultRequestDto, //Used for paging/sorting
BookCreateUpdateDto>, //Used to create/update a book
IBookAppService //implement the IBookAppService
{
private ISqlSugarRepository<BookAggregateRoot, Guid> _repository;
public BookAppService(ISqlSugarRepository<BookAggregateRoot, Guid> repository)
: base(repository)
{
_repository = repository;
}
public override async Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
{
RefAsync<int> total = 0;
//由于直接查询接口基本上都是有包含查询条件的,默认内置的查询接口将无法满足业务的需求,所以基本上多查询都是有进行重写的
var entities = await _repository._DbQueryable
//.WhereIF(!string.IsNullOrEmpty(input.ConfigKey), x => x.ConfigKey.Contains(input.ConfigKey!))
// .WhereIF(!string.IsNullOrEmpty(input.ConfigName), x => x.ConfigName!.Contains(input.ConfigName!))
// .WhereIF(input.StartTime is not null && input.EndTime is not null, x => x.CreationTime >= input.StartTime && x.CreationTime <= input.EndTime)
.ToPageListAsync(input.SkipCount, input.MaxResultCount, total);
return new PagedResultDto<BookDto>(total, await MapToGetListOutputDtosAsync(entities));
}
}
}
}

View File

@@ -0,0 +1,18 @@
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
namespace Acme.BookStore.Application.Services
{
public class TestService : ApplicationService
{
/// <summary>
/// 你好世界
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public string GetHelloWorld(string? name)
{
return name ?? "HelloWord";
}
}
}

View File

@@ -0,0 +1,23 @@
using Volo.Abp.Modularity;
using Acme.BookStore.Application.Contracts;
using Acme.BookStore.Domain;
using Yi.Framework.Bbs.Application;
using Yi.Framework.Ddd.Application;
using Yi.Framework.Rbac.Application;
namespace Acme.BookStore.Application
{
[DependsOn(
typeof(YiAbpApplicationContractsModule),
typeof(YiAbpDomainModule),
typeof(YiFrameworkRbacApplicationModule),
typeof(YiFrameworkBbsApplicationModule),
typeof(YiFrameworkDddApplicationModule)
)]
public class YiAbpApplicationModule : AbpModule
{
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain.Shared" Version="$(AbpVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.Domain.Shared\Yi.Framework.Bbs.Domain.Shared.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.Domain.Shared\Yi.Framework.Rbac.Domain.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Consts\" />
<Folder Include="Dtos\" />
<Folder Include="Etos\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Acme.BookStore.Domain.Shared.Enums
{
public enum BookTypeEnum
{
Undefined,
Adventure,
Biography,
Dystopia,
Fantastic,
Horror,
Science,
ScienceFiction,
Poetry
}
}

View File

@@ -0,0 +1,17 @@
using Volo.Abp.Domain;
using Volo.Abp.Modularity;
using Yi.Framework.Bbs.Domain.Shared;
using Yi.Framework.Rbac.Domain.Shared;
namespace Acme.BookStore.Domain.Shared
{
[DependsOn(
typeof(YiFrameworkRbacDomainSharedModule),
typeof(YiFrameworkBbsDomainSharedModule),
typeof(AbpDddDomainSharedModule))]
public class YiAbpDomainSharedModule : AbpModule
{
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<PackageReference Include="Volo.Abp.Ddd.Domain" Version="$(AbpVersion)" />
<PackageReference Include="Volo.Abp.Caching" Version="$(AbpVersion)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\framework\Yi.Framework.Mapster\Yi.Framework.Mapster.csproj" />
<ProjectReference Include="..\..\framework\Yi.Framework.SqlSugarCore.Abstractions\Yi.Framework.SqlSugarCore.Abstractions.csproj" />
<ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.Domain\Yi.Framework.Bbs.Domain.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.Domain\Yi.Framework.Rbac.Domain.csproj" />
<ProjectReference Include="..\Acme.BookStore.Domain.Shared\Acme.BookStore.Domain.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Managers\" />
<Folder Include="Repositories\" />
<Folder Include="EventHandlers\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using Acme.BookStore.Domain.Shared.Enums;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities.Auditing;
namespace Acme.BookStore.Domain.Entities
{
[SugarTable("Book")]
public class BookAggregateRoot : AuditedAggregateRoot<Guid>
{
[SugarColumn(IsPrimaryKey = true)]
public override Guid Id { get; protected set; }
public string Name { get; set; }
public BookTypeEnum Type { get; set; }
public DateTime PublishDate { get; set; }
public float Price { get; set; }
[SugarColumn(IsIgnore = true)]
public override ExtraPropertyDictionary ExtraProperties { get; protected set; }
}
}

View File

@@ -0,0 +1,26 @@
using Volo.Abp.Caching;
using Volo.Abp.Domain;
using Volo.Abp.Modularity;
using Acme.BookStore.Domain.Shared;
using Yi.Framework.Bbs.Domain;
using Yi.Framework.Mapster;
using Yi.Framework.Rbac.Domain;
namespace Acme.BookStore.Domain
{
[DependsOn(
typeof(YiAbpDomainSharedModule),
typeof(YiFrameworkRbacDomainModule),
typeof(YiFrameworkBbsDomainModule),
typeof(YiFrameworkMapsterModule),
typeof(AbpDddDomainModule),
typeof(AbpCachingModule)
)]
public class YiAbpDomainModule : AbpModule
{
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\common.props" />
<ItemGroup>
<ProjectReference Include="..\..\framework\Yi.Framework.Mapster\Yi.Framework.Mapster.csproj" />
<ProjectReference Include="..\..\framework\Yi.Framework.SqlSugarCore\Yi.Framework.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\bbs\Yi.Framework.Bbs.SqlSugarCore\Yi.Framework.Bbs.SqlSugarCore.csproj" />
<ProjectReference Include="..\..\module\rbac\Yi.Framework.Rbac.SqlSugarCore\Yi.Framework.Rbac.SqlSugarCore.csproj" />
<ProjectReference Include="..\Acme.BookStore.Domain\Acme.BookStore.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Repositories\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,50 @@
using Acme.BookStore.Domain.Entities;
using Acme.BookStore.Domain.Shared.Enums;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Guids;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Acme.BookStore.SqlSugarCore.DataSeeds
{
public class BookStoreDataSeed : IDataSeedContributor, ITransientDependency
{
private ISqlSugarRepository<BookAggregateRoot> _bookRepository;
private IGuidGenerator _guidGenerator;
public BookStoreDataSeed(ISqlSugarRepository<BookAggregateRoot> repository, IGuidGenerator guidGenerator)
{
_bookRepository = repository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
if (!await _bookRepository.IsAnyAsync(x => true))
{
await _bookRepository.InsertAsync(
new BookAggregateRoot
{
Name = "1984",
Type = BookTypeEnum.Dystopia,
PublishDate = new DateTime(1949, 6, 8),
Price = 19.84f
},
autoSave: true
);
await _bookRepository.InsertAsync(
new BookAggregateRoot
{
Name = "The Hitchhiker's Guide to the Galaxy",
Type = BookTypeEnum.ScienceFiction,
PublishDate = new DateTime(1995, 9, 27),
Price = 42.0f
},
autoSave: true
);
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More