feat:框架基础设施搭建

This commit is contained in:
橙子
2023-04-12 22:52:09 +08:00
parent 5efdffcda8
commit 18696ec542
572 changed files with 26077 additions and 25 deletions

View File

@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using Yi.Framework.Infrastructure.CurrentUsers;
using Yi.Framework.Infrastructure.CurrentUsers.Accessor;
namespace Yi.Framework.Infrastructure.AspNetCore
{
public static class CurrentUserAddExtensions
{
public static IServiceCollection AddCurrentUserServer(this IServiceCollection services)
{
services.AddSingleton<ICurrentPrincipalAccessor, HttpContextCurrentPrincipalAccessor>();
return services.AddTransient<ICurrentUser, CurrentUser>();
}
}
}

View File

@@ -1,7 +0,0 @@
namespace Yi.Framework.Infrastructure
{
public class Class1
{
}
}

View File

@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Infrastructure.Const
{
/// <summary>
/// 定义公共文件常量
/// </summary>
public class PathConst
{
public const string wwwroot = "wwwroot";
public const string DataTemplate = "_DataTemplate";
public const string DataExport = "_DataExport";
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Infrastructure.Const
{
public class TokenTypeConst
{
public const string Id = nameof(Id);
public const string UserName = nameof(UserName);
public const string TenantId = nameof(TenantId);
public const string Email = nameof(Email);
public const string PhoneNumber = nameof(PhoneNumber);
public const string Roles = nameof(Roles);
public const string Permission = nameof(Permission);
}
}

View File

@@ -0,0 +1,32 @@
using System.Security.Claims;
using Yi.Framework.Infrastructure.Utils;
namespace Yi.Framework.Infrastructure.CurrentUsers.Accessor
{
public abstract class CurrentPrincipalAccessorBase : ICurrentPrincipalAccessor
{
public ClaimsPrincipal Principal => _currentPrincipal.Value ?? GetClaimsPrincipal();
private readonly AsyncLocal<ClaimsPrincipal> _currentPrincipal = new AsyncLocal<ClaimsPrincipal>();
protected abstract ClaimsPrincipal GetClaimsPrincipal();
public virtual IDisposable Change(ClaimsPrincipal principal)
{
return SetCurrent(principal);
}
private IDisposable SetCurrent(ClaimsPrincipal principal)
{
var parent = Principal;
_currentPrincipal.Value = principal;
return new DisposeAction<ValueTuple<AsyncLocal<ClaimsPrincipal>, ClaimsPrincipal>>(static (state) =>
{
var (currentPrincipal, parent) = state;
currentPrincipal.Value = parent;
}, (_currentPrincipal, parent));
}
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Infrastructure.CurrentUsers.Accessor
{
public class HttpContextCurrentPrincipalAccessor : ThreadCurrentPrincipalAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HttpContextCurrentPrincipalAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override ClaimsPrincipal GetClaimsPrincipal()
{
return _httpContextAccessor.HttpContext?.User ?? base.GetClaimsPrincipal();
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Security.Claims;
namespace Yi.Framework.Infrastructure.CurrentUsers.Accessor
{
public interface ICurrentPrincipalAccessor
{
ClaimsPrincipal Principal { get; }
IDisposable Change(ClaimsPrincipal principal);
}
}

View File

@@ -0,0 +1,13 @@
using System.Security.Claims;
namespace Yi.Framework.Infrastructure.CurrentUsers.Accessor
{
public class StaticPrincipalAccessor : CurrentPrincipalAccessorBase
{
public static ClaimsPrincipal ClaimsPrincipal { get; set; }
protected override ClaimsPrincipal GetClaimsPrincipal()
{
return ClaimsPrincipal;
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Security.Claims;
namespace Yi.Framework.Infrastructure.CurrentUsers.Accessor
{
public class ThreadCurrentPrincipalAccessor : CurrentPrincipalAccessorBase
{
protected override ClaimsPrincipal GetClaimsPrincipal()
{
return Thread.CurrentPrincipal as ClaimsPrincipal;
}
}
}

View File

@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Infrastructure.Const;
using Yi.Framework.Infrastructure.CurrentUsers.Accessor;
namespace Yi.Framework.Infrastructure.CurrentUsers
{
public class CurrentUser : ICurrentUser
{
private readonly ICurrentPrincipalAccessor _principalAccessor;
public CurrentUser(ICurrentPrincipalAccessor principalAccessor)
{
_principalAccessor = principalAccessor;
}
public bool IsAuthenticated => Id != 0;
public long Id => FindUserId();
public string UserName => this.FindClaimValue(TokenTypeConst.UserName);
/// <summary>
/// 暂时为默认值
/// </summary>
public Guid TenantId { get; set; } = Guid.Empty;
public string Email => FindClaimValue(TokenTypeConst.Email);
public bool EmailVerified => false;
public string PhoneNumber => FindClaimValue(TokenTypeConst.PhoneNumber);
public bool PhoneNumberVerified => false;
public string[]? Roles => this.FindClaims(TokenTypeConst.Roles).Select(c => c.Value).Distinct().ToArray();
public string[]? Permission => this.FindClaims(TokenTypeConst.Permission).Select(c => c.Value).Distinct().ToArray();
public virtual Claim FindClaim(string claimType)
{
return _principalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == claimType);
}
public virtual Claim[] FindClaims(string claimType)
{
return _principalAccessor.Principal?.Claims.Where(c => c.Type == claimType).ToArray() ?? new Claim[0];
}
public virtual Claim[] GetAllClaims()
{
return _principalAccessor.Principal?.Claims.ToArray() ?? new Claim[0];
}
public string FindClaimValue(string claimType)
{
return FindClaim(claimType)?.Value;
}
public long FindUserId()
{
var userIdOrNull = _principalAccessor.Principal?.Claims?.FirstOrDefault(c => c.Type == TokenTypeConst.Id);
if (userIdOrNull == null || string.IsNullOrWhiteSpace(userIdOrNull.Value))
{
return 0;
}
if (long.TryParse(userIdOrNull.Value, out long userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Infrastructure.CurrentUsers
{
public interface ICurrentUser
{
public bool IsAuthenticated { get; }
public long Id { get; }
public string UserName { get; }
public Guid TenantId { get; }
public string Email { get; }
public bool EmailVerified { get; }
public string PhoneNumber { get; }
public bool PhoneNumberVerified { get; }
public string[]? Roles { get; }
public string[]? Permission { get; }
}
}

View File

@@ -0,0 +1,47 @@
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Infrastructure.Sqlsugar
{
public class DbConnOptions
{
/// <summary>
/// 连接字符串,必填
/// </summary>
public string? Url { get; set; }
/// <summary>
/// 数据库类型
/// </summary>
public DbType? DbType { get; set; }
/// <summary>
/// 开启种子数据
/// </summary>
public bool EnabledDbSeed { get; set; } = false;
/// <summary>
/// 开启读写分离
/// </summary>
public bool EnabledReadWrite { get; set; } = false;
/// <summary>
/// 开启codefirst
/// </summary>
public bool EnabledCodeFirst { get; set; } = false;
/// <summary>
/// 实体程序集
/// </summary>
public List<string>? EntityAssembly { get; set; }
/// <summary>
/// 读写分离
/// </summary>
public List<string>? ReadUrl { get; set; }
}
}

View File

@@ -0,0 +1,147 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Infrastructure.CurrentUsers;
namespace Yi.Framework.Infrastructure.Sqlsugar
{
public class SqlSugarDbContext
{
/// <summary>
/// SqlSugar 客户端
/// </summary>
public ISqlSugarClient SqlSugarClient { get; set; }
protected ICurrentUser _currentUser;
protected ILogger<SqlSugarDbContext> _logger;
protected IOptions<DbConnOptions> _options;
public SqlSugarDbContext(IOptions<DbConnOptions> options, ICurrentUser currentUser, ILogger<SqlSugarDbContext> logger)
{
_currentUser = currentUser;
_logger = logger;
_options = options;
var dbConnOptions = options.Value;
#region options
if (dbConnOptions.DbType is null)
{
throw new ArgumentException(SqlsugarConst.DbType配置为空);
}
var slavaConFig = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException(SqlsugarConst.);
}
var readCon = dbConnOptions.ReadUrl;
readCon.ForEach(s =>
{
//如果是动态saas分库这里的连接串都不能写死需要动态添加这里只配置共享库的连接
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
});
}
#endregion
SqlSugarClient = new SqlSugarScope(new ConnectionConfig()
{
//准备添加分表分库
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
MoreSettings = new ConnMoreSettings()
{
DisableNvarchar = true
},
SlaveConnectionConfigs = slavaConFig,
//设置codefirst非空值判断
ConfigureExternalServices = new ConfigureExternalServices
{
EntityService = (c, p) =>
{
//高版C#写法 支持string?和string
if (new NullabilityInfoContext()
.Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
}
}
},
db =>
{
db.Aop.DataExecuting = (oldValue, entityInfo) =>
{
//switch (entityInfo.OperationType)
//{
// case DataFilterType.UpdateByObject:
// if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
// {
// entityInfo.SetValue(DateTime.Now);
// }
// if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
// {
// if (_currentUser != null)
// {
// entityInfo.SetValue(_currentUser.Id);
// }
// }
// break;
// case DataFilterType.InsertByObject:
// if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
// {
// entityInfo.SetValue(DateTime.Now);
// }
// if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
// {
// if (_currentUser != null)
// {
// entityInfo.SetValue(_currentUser.Id);
// }
// }
// //插入时需要租户id,先预留
// if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
// {
// //if (this.CurrentTenant is not null)
// //{
// // entityInfo.SetValue(this.CurrentTenant.Id);
// //}
// }
// break;
//}
};
db.Aop.OnLogExecuting = (s, p) =>
{
StringBuilder sb = new StringBuilder();
//sb.Append("执行SQL:" + s.ToString());
//foreach (var i in p)
//{
// sb.Append($"\r\n参数:{i.ParameterName},参数值:{i.Value}");
//}
sb.Append($"\r\n 完整SQL{UtilMethods.GetSqlString(DbType.MySql, s, p)}");
logger?.LogDebug(sb.ToString());
};
//扩展
OnSqlSugarClientConfig(db);
});
}
//上下文对象扩展
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Infrastructure.Sqlsugar
{
public class SqlsugarConst
{
public const string = "开启读写分离后,读库连接不能为空";
public const string DbType配置为空 = "DbType配置为空必须选择一个数据库类型";
}
}

View File

@@ -0,0 +1,19 @@
using Microsoft.Extensions.DependencyInjection;
namespace Yi.Framework.Infrastructure.Sqlsugar
{
/// <summary>
/// 这一块,需要做成上下文对象,会进行重构
/// </summary>
public static class SqlsugarExtensions
{
//使用上下文对象
public static void AddDbSqlsugarContextServer(this IServiceCollection services)
{
services.AddSingleton(x => x.GetRequiredService<SqlSugarDbContext>().SqlSugarClient);
services.AddSingleton<SqlSugarDbContext>();
}
}
}

View File

@@ -0,0 +1,26 @@
using Furion;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Yi.Framework.Infrastructure.AspNetCore;
using Yi.Framework.Infrastructure.Sqlsugar;
namespace Yi.Framework.Infrastructure;
public class Startup : AppStartup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCurrentUserServer();
services.Configure<DbConnOptions>(App.Configuration.GetSection("DbConnOptions"));
services.AddDbSqlsugarContextServer();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}

View File

@@ -0,0 +1,27 @@
using System.Diagnostics.CodeAnalysis;
namespace Yi.Framework.Infrastructure.Utils
{
public class DisposeAction<T> : IDisposable
{
private readonly Action<T> _action;
private readonly T _parameter;
/// <summary>
/// Creates a new <see cref="DisposeAction"/> object.
/// </summary>
/// <param name="action">Action to be executed when this object is disposed.</param>
/// <param name="parameter">The parameter of the action.</param>
public DisposeAction(Action<T> action, T parameter)
{
_action = action;
_parameter = parameter;
}
public void Dispose()
{
_action(_parameter);
}
}
}

View File

@@ -7,7 +7,7 @@
</PropertyGroup>
<ItemGroup>
<Folder Include="Sqlsugar\" />
<ProjectReference Include="..\Yi.Furion.Rbac.Core\Yi.Furion.Rbac.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,14 @@
namespace Yi.Furion.Rbac.Application;
using SqlSugar;
namespace Yi.Furion.Rbac.Application;
public class SystemService : ISystemService, ITransient
{
private readonly ISqlSugarClient _sqlSugarClient;
public SystemService(ISqlSugarClient sqlSugarClient)
{
_sqlSugarClient=sqlSugarClient;
}
public string GetDescription()
{
return "让 .NET 开发更简单,更通用,更流行。";

View File

@@ -22,7 +22,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Yi.Furion.Rbac.Core\Yi.Furion.Rbac.Core.csproj" />
<ProjectReference Include="..\Yi.Framework.Infrastructure\Yi.Framework.Infrastructure.csproj" />
<ProjectReference Include="..\Yi.Furion.Rbac.EntityFramework.Core\Yi.Furion.Rbac.Sqlsugar.Core.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Yi.Furion.Rbac.EntityFramework.Core\Yi.Furion.Rbac.EntityFramework.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -7,5 +7,20 @@
"Microsoft.EntityFrameworkCore": "Information"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
//数据库类型列表
"DbList": [ "Sqlite", "Mysql", "Sqlserver", "Oracle" ],
"DbConnOptions": {
"Url": "DataSource=yi-sqlsugar-dev.db",
"DbType": "Sqlite",
"EnabledReadWrite": false,
"EnabledCodeFirst": false,
"EntityAssembly": null,
"ReadUrl": [
"DataSource=[xxxx]", //Sqlite
"server=[xxxx];port=3306;database=[xxxx];user id=[xxxx];password=[xxxx]", //Mysql
"Data Source=[xxxx];Initial Catalog=[xxxx];User ID=[xxxx];password=[xxxx]" //Sqlserver
]
}
}