增加数据库管理

This commit is contained in:
LemonNoCry 2023-04-14 10:43:47 +08:00
parent 8183be9d58
commit e9f1ef5c01
No known key found for this signature in database
16 changed files with 789 additions and 377 deletions

View File

@ -106,6 +106,7 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\Systems\" />
<Folder Include="wwwroot\BlogCore.Data.excel\" />
</ItemGroup>

View File

@ -1957,6 +1957,11 @@
找不到指定资源
</summary>
</member>
<member name="T:Blog.Core.Model.Systems.DataBase.DataBaseReadType">
<summary>
数据库读取类型
</summary>
</member>
<member name="T:Blog.Core.Model.TableModel`1">
<summary>
表格数据,支持分页

View File

@ -1299,6 +1299,46 @@
<param name="id"></param>
<returns></returns>
</member>
<member name="T:Blog.Core.Api.Controllers.Systems.DataBaseController">
<summary>
数据库管理
</summary>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.DataBaseController.GetAllConfig">
<summary>
获取库配置
</summary>
<returns></returns>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.DataBaseController.GetTableInfoList(System.String,Blog.Core.Model.Systems.DataBase.DataBaseReadType)">
<summary>
获取表信息
</summary>
<param name="configId">配置Id</param>
<param name="readType">读取类型</param>
<returns></returns>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.DataBaseController.GetColumnInfosByTableName(System.String,System.String,Blog.Core.Model.Systems.DataBase.DataBaseReadType)">
<summary>
获取表字段
</summary>
<param name="tableName">表名</param>
<param name="configId">ConfigId</param>
<param name="readType">读取类型</param>
<returns></returns>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.DataBaseController.PutTableEditRemark(Blog.Core.Model.Systems.DataBase.EditTableInput)">
<summary>
编辑表备注
</summary>
<param name="input"></param>
</member>
<member name="M:Blog.Core.Api.Controllers.Systems.DataBaseController.PutColumnEditRemark(Blog.Core.Model.Systems.DataBase.EditColumnInput)">
<summary>
编辑列备注
</summary>
<param name="input"></param>
</member>
<member name="T:Blog.Core.Api.Controllers.Tenant.TenantByDbController">
<summary>
多租户-多库方案 测试

View File

@ -16,6 +16,7 @@ namespace Blog.Core.Controllers
response = data,
};
}
// [NonAction]
//public MessageModel<T> Success<T>(T data, string msg = "成功",bool success = true)
//{
@ -36,6 +37,7 @@ namespace Blog.Core.Controllers
response = null,
};
}
[NonAction]
public MessageModel<string> Failed(string msg = "失败", int status = 500)
{
@ -47,6 +49,7 @@ namespace Blog.Core.Controllers
response = null,
};
}
[NonAction]
public MessageModel<T> Failed<T>(string msg = "失败", int status = 500)
{
@ -58,22 +61,22 @@ namespace Blog.Core.Controllers
response = default,
};
}
[NonAction]
public MessageModel<PageModel<T>> SuccessPage<T>(int page, int dataCount, int pageSize, List<T> data, int pageCount, string msg = "获取成功")
{
[NonAction]
public MessageModel<PageModel<T>> SuccessPage<T>(int page, int dataCount, int pageSize, List<T> data,
int pageCount, string msg = "获取成功")
{
return new MessageModel<PageModel<T>>()
{
success = true,
msg = msg,
response = new PageModel<T>(page, dataCount, pageSize, data)
};
}
[NonAction]
public MessageModel<PageModel<T>> SuccessPage<T>(PageModel<T> pageModel, string msg = "获取成功")
{
return new MessageModel<PageModel<T>>()
{
success = true,

View File

@ -0,0 +1,188 @@
using System.Diagnostics.CodeAnalysis;
using Blog.Core.Common;
using Blog.Core.Common.DB;
using Blog.Core.Controllers;
using Blog.Core.Model;
using Blog.Core.Model.Models;
using Blog.Core.Model.Systems.DataBase;
using Blog.Core.Model.Tenants;
using Mapster;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
namespace Blog.Core.Api.Controllers.Systems;
/// <summary>
/// 数据库管理
/// </summary>
[Route("api/Systems/[controller]/[action]")]
[ApiController]
[Authorize(Permissions.Name)]
public class DataBaseController : BaseApiController
{
private readonly ISqlSugarClient _db;
public DataBaseController(ISqlSugarClient db)
{
_db = db;
}
[return: NotNull]
public ISqlSugarClient GetTenantDb(string configId)
{
if (!_db.AsTenant().IsAnyConnection(configId))
{
var tenant = _db.Queryable<SysTenant>().WithCache()
.Where(s => s.TenantType == TenantTypeEnum.Db)
.Where(s => s.ConfigId == configId)
.First();
if (tenant != null)
{
_db.AsTenant().AddConnection(tenant.GetConnectionConfig());
}
}
var db = _db.AsTenant().GetConnectionScope(configId);
if (db is null)
{
throw new ApplicationException("无效的数据库配置");
}
return db;
}
/// <summary>
/// 获取库配置
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<MessageModel<List<DatabaseOutput>>> GetAllConfig()
{
//增加多租户的连接
var allConfigs = new List<ConnectionConfig>(BaseDBConfig.AllConfigs);
var tenants = await _db.Queryable<SysTenant>().WithCache()
.Where(s => s.TenantType == TenantTypeEnum.Db)
.ToListAsync();
if (tenants.Any())
{
allConfigs.AddRange(tenants.Select(tenant => tenant.GetConnectionConfig()));
}
var configs = await Task.FromResult(allConfigs);
return Success(configs.Adapt<List<DatabaseOutput>>());
}
/// <summary>
/// 获取表信息
/// </summary>
/// <param name="configId">配置Id</param>
/// <param name="readType">读取类型</param>
/// <returns></returns>
[HttpGet]
public MessageModel<List<DbTableInfo>> GetTableInfoList(string configId,
DataBaseReadType readType = DataBaseReadType.Db)
{
if (configId.IsNullOrEmpty())
{
configId = MainDb.CurrentDbConnId;
}
var provider = GetTenantDb(configId);
List<DbTableInfo> data = null;
switch (readType)
{
case DataBaseReadType.Db:
data = provider.DbMaintenance.GetTableInfoList(false);
break;
case DataBaseReadType.Entity:
if (EntityUtility.TenantEntitys.TryGetValue(configId, out var types))
{
data = types.Select(s => provider.EntityMaintenance.GetEntityInfo(s))
.Select(s => new {Name = s.DbTableName, Description = s.TableDescription})
.Adapt<List<DbTableInfo>>();
}
break;
}
return Success(data);
}
/// <summary>
/// 获取表字段
/// </summary>
/// <param name="tableName">表名</param>
/// <param name="configId">ConfigId</param>
/// <param name="readType">读取类型</param>
/// <returns></returns>
[HttpGet]
public MessageModel<List<DbColumnInfoOutput>> GetColumnInfosByTableName(string tableName, string configId = null,
DataBaseReadType readType = DataBaseReadType.Db)
{
if (string.IsNullOrWhiteSpace(tableName))
return Failed<List<DbColumnInfoOutput>>("表名不能为空");
if (configId.IsNullOrEmpty())
{
configId = MainDb.CurrentDbConnId;
}
List<DbColumnInfoOutput> data = null;
var provider = GetTenantDb(configId);
switch (readType)
{
case DataBaseReadType.Db:
data = provider.DbMaintenance.GetColumnInfosByTableName(tableName, false)
.Adapt<List<DbColumnInfoOutput>>();
break;
case DataBaseReadType.Entity:
if (EntityUtility.TenantEntitys.TryGetValue(configId, out var types))
{
var type = types.FirstOrDefault(s => s.Name == tableName);
data = provider.EntityMaintenance.GetEntityInfo(type).Columns.Adapt<List<DbColumnInfoOutput>>();
}
break;
}
return Success(data);
}
/// <summary>
/// 编辑表备注
/// </summary>
/// <param name="input"></param>
[HttpPut]
public MessageModel PutTableEditRemark([FromBody] EditTableInput input)
{
var provider = GetTenantDb(input.ConfigId);
if (provider.DbMaintenance.IsAnyTableRemark(input.TableName))
{
provider.DbMaintenance.DeleteTableRemark(input.TableName);
}
provider.DbMaintenance.AddTableRemark(input.TableName, input.Description);
return Success();
}
/// <summary>
/// 编辑列备注
/// </summary>
/// <param name="input"></param>
[HttpPut]
public MessageModel PutColumnEditRemark([FromBody] EditColumnInput input)
{
var provider = GetTenantDb(input.ConfigId);
if (provider.DbMaintenance.IsAnyColumnRemark(input.DbColumnName, input.TableName))
{
provider.DbMaintenance.DeleteColumnRemark(input.DbColumnName, input.TableName);
}
provider.DbMaintenance.AddColumnRemark(input.DbColumnName, input.TableName, input.ColumnDescription);
return Success();
}
}

View File

@ -18,6 +18,9 @@
<ItemGroup>
<PackageReference Include="Magicodes.IE.Excel" Version="2.6.4" />
<PackageReference Include="InitQ" Version="1.0.0.12" />
<PackageReference Include="log4net" Version="2.0.15" />
<PackageReference Include="Mapster" Version="7.3.0" />
<PackageReference Include="Mapster.Core" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />

View File

@ -3,18 +3,23 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SqlSugar;
namespace Blog.Core.Common.DB
{
public class BaseDBConfig
{
public static readonly List<ConnectionConfig> AllConfigs = new(); //所有库配置
public static readonly List<SlaveConnectionConfig> AllSlaveConfigs = new(); //从库配置
public static List<ConnectionConfig> ValidConfig = new(); //有效的库连接(除去Log库)
public static ConnectionConfig LogConfig; //日志库
/* GitHub的历史记录
* appsettings.json设置为true的第一个db连接
*/
public static (List<MutiDBOperate> allDbs, List<MutiDBOperate> slaveDbs) MutiConnectionString => MutiInitConn();
public static List<ConnectionConfig> AllConfig = new(); //所有的库连接
public static List<ConnectionConfig> ValidConfig = new(); //有效的库连接(除去Log库)
public static ConnectionConfig LogConfig; //日志库
private static string DifDBConnOfSecurity(params string[] conn)
{

View File

@ -0,0 +1,53 @@
using Blog.Core.Common.Extensions;
using Blog.Core.Model;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace Blog.Core.Common.DB;
public class EntityUtility
{
private static readonly Lazy<Dictionary<string, List<Type>>> _tenantEntitys = new(() =>
{
Dictionary<string, List<Type>> dic = new Dictionary<string, List<Type>>();
var assembly = Assembly.Load("Blog.Core.Model");
//扫描 实体
foreach (var type in assembly.GetTypes().Where(s => s.IsClass && !s.IsAbstract))
{
var tenant = type.GetCustomAttribute<TenantAttribute>();
if (tenant != null)
{
dic.TryAdd(tenant.configId.ToString(), type);
continue;
}
if (type.IsSubclassOf(typeof(RootEntityTkey<>)))
{
dic.TryAdd(MainDb.CurrentDbConnId, type);
continue;
}
var table = type.GetCustomAttribute<SugarTable>();
if (table != null)
{
dic.TryAdd(MainDb.CurrentDbConnId, type);
continue;
}
Debug.Assert(type.Namespace != null, "type.Namespace != null");
if (type.Namespace.StartsWith("Blog.Core.Model.Models"))
{
dic.TryAdd(MainDb.CurrentDbConnId, type);
continue;
}
}
return dic;
});
public static Dictionary<string, List<Type>> TenantEntitys => _tenantEntitys.Value;
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Blog.Core.Common.Extensions;
public static class DictionaryExtensions
{
public static void TryAdd<TKey, TValue>(this IDictionary<TKey, List<TValue>> dic, TKey key, TValue value)
{
if (dic.TryGetValue(key, out var old))
{
old.Add(value);
}
else
{
dic.Add(key, new List<TValue> {value});
}
}
}

View File

@ -14,6 +14,7 @@ using System.Linq;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Blog.Core.Model.Models;
namespace Blog.Core.AuthHelper
{
@ -40,7 +41,9 @@ namespace Blog.Core.AuthHelper
/// <param name="accessor"></param>
/// <param name="userServices"></param>
/// <param name="user"></param>
public PermissionHandler(IAuthenticationSchemeProvider schemes, IRoleModulePermissionServices roleModulePermissionServices, IHttpContextAccessor accessor, ISysUserInfoServices userServices, IUser user)
public PermissionHandler(IAuthenticationSchemeProvider schemes,
IRoleModulePermissionServices roleModulePermissionServices, IHttpContextAccessor accessor,
ISysUserInfoServices userServices, IUser user)
{
_accessor = accessor;
_userServices = userServices;
@ -50,7 +53,8 @@ namespace Blog.Core.AuthHelper
}
// 重写异步处理程序
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
var httpContext = _accessor.HttpContext;
@ -105,14 +109,14 @@ namespace Blog.Core.AuthHelper
var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler handler && await handler.HandleRequestAsync())
if (await handlers.GetHandlerAsync(httpContext, scheme.Name) is IAuthenticationRequestHandler
handler && await handler.HandleRequestAsync())
{
context.Fail();
return;
}
}
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
@ -145,6 +149,9 @@ namespace Blog.Core.AuthHelper
select item.Value).ToList();
}
//超级管理员 默认拥有所有权限
if (currentUserRoles.All(s => s != "SuperAdmin"))
{
var isMatchRole = false;
var permisssionRoles = requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role));
foreach (var item in permisssionRoles)
@ -169,6 +176,7 @@ namespace Blog.Core.AuthHelper
context.Fail();
return;
}
}
// 判断token是否过期过期则重新登录
var isExp = false;
@ -176,12 +184,18 @@ namespace Blog.Core.AuthHelper
// ids4
if (Permissions.IsUseIds4)
{
isExp = (httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) != null && DateHelper.StampToDateTime(httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) >= DateTime.Now;
isExp = (httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) != null &&
DateHelper.StampToDateTime(httpContext.User.Claims
.SingleOrDefault(s => s.Type == "exp")?.Value) >= DateTime.Now;
}
else
{
// jwt
isExp = (httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) != null && DateTime.Parse(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now;
isExp =
(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)
?.Value) != null &&
DateTime.Parse(httpContext.User.Claims
.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now;
}
if (!isExp)
@ -193,13 +207,15 @@ namespace Blog.Core.AuthHelper
//校验签发时间
if (!Permissions.IsUseIds4)
{
var value = httpContext.User.Claims.SingleOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value;
var value = httpContext.User.Claims
.SingleOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value;
if (value != null)
{
var user = await _userServices.QueryById(_user.ID, true);
if (user.CriticalModifyTime > value.ObjToDate())
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "很抱歉,授权已失效,请重新授权").MessageModel;
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "很抱歉,授权已失效,请重新授权")
.MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
@ -212,7 +228,8 @@ namespace Blog.Core.AuthHelper
}
//判断没有登录时是否访问登录的url,并且是Post请求并且是form表单提交类型否则为失败
if (!(questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType)))
if (!(questUrl.Equals(requirement.LoginPath.ToLower(), StringComparison.Ordinal) &&
(!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType)))
{
context.Fail();
return;

View File

@ -27,17 +27,9 @@ namespace Blog.Core.Extensions
// 默认添加主数据库连接
MainDb.CurrentDbConnId = AppSettings.app(new string[] {"MainDB"});
// SqlSugarScope是线程安全可使用单例注入
// 参考https://www.donet5.com/Home/Doc?typeId=1181
services.AddSingleton<ISqlSugarClient>(o =>
{
var memoryCache = o.GetRequiredService<IMemoryCache>();
// 从库
var listConfig_Slave = new List<SlaveConnectionConfig>();
BaseDBConfig.MutiConnectionString.slaveDbs.ForEach(s =>
{
listConfig_Slave.Add(new SlaveConnectionConfig()
BaseDBConfig.AllSlaveConfigs.Add(new SlaveConnectionConfig()
{
HitRate = s.HitRate,
ConnectionString = s.Connection
@ -61,11 +53,10 @@ namespace Blog.Core.Extensions
SqlServerCodeFirstNvarchar = true,
},
// 从库
SlaveConnectionConfigs = listConfig_Slave,
SlaveConnectionConfigs = BaseDBConfig.AllSlaveConfigs,
// 自定义特性
ConfigureExternalServices = new ConfigureExternalServices()
{
DataInfoCacheService = new SqlSugarMemoryCacheService(memoryCache),
EntityService = (property, column) =>
{
if (column.IsPrimarykey && property.PropertyType == typeof(int))
@ -85,7 +76,7 @@ namespace Blog.Core.Extensions
BaseDBConfig.ValidConfig.Add(config);
}
BaseDBConfig.AllConfig.Add(config);
BaseDBConfig.AllConfigs.Add(config);
});
if (BaseDBConfig.LogConfig is null)
@ -93,14 +84,27 @@ namespace Blog.Core.Extensions
throw new ApplicationException("未配置Log库连接");
}
return new SqlSugarScope(BaseDBConfig.AllConfig, db =>
// SqlSugarScope是线程安全可使用单例注入
// 参考https://www.donet5.com/Home/Doc?typeId=1181
services.AddSingleton<ISqlSugarClient>(o =>
{
var memoryCache = o.GetRequiredService<IMemoryCache>();
foreach (var config in BaseDBConfig.AllConfigs)
{
config.ConfigureExternalServices.DataInfoCacheService = new SqlSugarMemoryCacheService(memoryCache);
}
return new SqlSugarScope(BaseDBConfig.AllConfigs, db =>
{
BaseDBConfig.ValidConfig.ForEach(config =>
{
var dbProvider = db.GetConnectionScope((string) config.ConfigId);
// 打印SQL语句
dbProvider.Aop.OnLogExecuting = (s, parameters) => SqlSugarAop.OnLogExecuting(dbProvider, s, parameters, config);
dbProvider.Aop.OnLogExecuting = (s, parameters) =>
SqlSugarAop.OnLogExecuting(dbProvider, s, parameters, config);
// 数据审计
dbProvider.Aop.DataExecuting = SqlSugarAop.DataExecuting;

View File

@ -0,0 +1,10 @@
namespace Blog.Core.Model.Systems.DataBase;
/// <summary>
/// 数据库读取类型
/// </summary>
public enum DataBaseReadType
{
Db,
Entity
}

View File

@ -0,0 +1,10 @@
using SqlSugar;
namespace Blog.Core.Model.Systems.DataBase;
public class DatabaseOutput
{
public string ConfigId { get; set; }
public DbType DbType { get; set; }
}

View File

@ -0,0 +1,36 @@
namespace Blog.Core.Model.Systems.DataBase;
public class DbColumnInfoOutput
{
public string TableName { get; set; }
public int TableId { get; set; }
public string DbColumnName { get; set; }
public string PropertyName { get; set; }
public string DataType { get; set; }
public int Length { get; set; }
public string ColumnDescription { get; set; }
public string DefaultValue { get; set; }
public bool IsNullable { get; set; }
public bool IsIdentity { get; set; }
public bool IsPrimarykey { get; set; }
public object Value { get; set; }
public int DecimalDigits { get; set; }
public int Scale { get; set; }
public bool IsArray { get; set; }
internal bool IsJson { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace Blog.Core.Model.Systems.DataBase;
public class EditColumnInput
{
public string ConfigId { get; set; }
public string TableName { get; set; }
public string DbColumnName { get; set; }
public string ColumnDescription { get; set; }
}

View File

@ -0,0 +1,10 @@
namespace Blog.Core.Model.Systems.DataBase;
public class EditTableInput
{
public string ConfigId { get; set; }
public string TableName { get; set; }
public string Description { get; set; }
}