Merge branch 'refs/heads/dev' into dev-blazor

# Conflicts:
#	src/admin/Bootstrap.DataAccess/Bootstrap.DataAccess.csproj
This commit is contained in:
Argo Window10 2019-11-17 13:24:19 +08:00
commit bda547f321
167 changed files with 1671 additions and 1198 deletions

View File

@ -33,7 +33,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MongoDB", "MongoDB", "{A06A
db\MongoDB\Dicts.js = db\MongoDB\Dicts.js
db\MongoDB\Groups.js = db\MongoDB\Groups.js
db\MongoDB\init.js = db\MongoDB\init.js
db\MongoDB\install.cmd = db\MongoDB\install.cmd
db\MongoDB\install.ps1 = db\MongoDB\install.ps1
db\MongoDB\install.sh = db\MongoDB\install.sh
db\MongoDB\Navigations.js = db\MongoDB\Navigations.js
db\MongoDB\Roles.js = db\MongoDB\Roles.js
@ -43,6 +43,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MySQL", "MySQL", "{084E2E94-6B7D-4D3E-9BF1-6972427FBF80}"
ProjectSection(SolutionItems) = preProject
db\MySQL\initData.sql = db\MySQL\initData.sql
db\MySQL\install.ps1 = db\MySQL\install.ps1
db\MySQL\install.sh = db\MySQL\install.sh
db\MySQL\install.sql = db\MySQL\install.sql
db\MySQL\my.ini = db\MySQL\my.ini

View File

@ -7,9 +7,14 @@
<LangVersion>latest</LangVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)src\Keys\Longbow.Utility.snk</AssemblyOriginatorKeyFile>
<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile>
</PropertyGroup>
<Target Condition=" $(IsWebProject) == true Or $(IsTestProject) == true" Name="PostBuild" AfterTargets="PostBuildEvent">
<Message Text="Copy lic file -> $(TargetDir)" Importance="high" />
<Copy Condition="'$(OS)' == 'Windows_NT'" DestinationFolder="$(TargetDir)" SourceFiles="$(MSBuildThisFileDirectory)src\admin\keys\Longbow.lic" SkipUnchangedFiles="true" />
<Copy Condition="'$(OS)' == 'UNIX'" DestinationFolder="$(TargetDir)" SourceFiles="$(MSBuildThisFileDirectory)src/admin/keys/Longbow.lic" SkipUnchangedFiles="true" />
</Target>
<Target Condition=" $(IsWebProject) == true " Name="PostPublish" AfterTargets="Publish">
<Message Text="Publish lic file -> $(PublishDir)" Importance="high" />
<Copy Condition="'$(OS)' == 'Windows_NT'" DestinationFolder="$(PublishDir)" SourceFiles="$(MSBuildThisFileDirectory)src\admin\keys\Longbow.lic" SkipUnchangedFiles="true" />
@ -19,11 +24,4 @@
<Copy Condition="'$(OS)' == 'UNIX'" DestinationFolder="$(PublishDir)" SourceFiles="$(MSBuildThisFileDirectory)src/admin/Bootstrap.Admin/BootstrapAdmin.db" SkipUnchangedFiles="true" />
</Target>
<Target Condition=" $(IsWebProject) == true " Name="PostBuild" AfterTargets="PostBuildEvent">
<Message Text="Copy lic file -> $(TargetDir)" Importance="high" />
<Copy Condition="'$(OS)' == 'Windows_NT'" DestinationFolder="$(TargetDir)" SourceFiles="$(MSBuildThisFileDirectory)src\admin\keys\Longbow.lic" SkipUnchangedFiles="true" />
<Message Text="Copy db file -> $(TargetDir)" Importance="high" />
<Copy Condition="'$(OS)' == 'UNIX'" DestinationFolder="$(TargetDir)" SourceFiles="$(MSBuildThisFileDirectory)src/admin/keys/Longbow.lic" SkipUnchangedFiles="true" />
</Target>
</Project>

View File

@ -611,6 +611,30 @@
"IsResource": NumberInt(0),
"Application": "BA"
},
{
"_id": ObjectId("1bd7b8445fa31256f77e4b9c"),
"ParentId": "5bd7b8445fa31256f77e4b9a",
"Name": "暂停",
"Order": NumberInt(20),
"Icon": "fa fa-fa",
"Url": "pause",
"Category": "0",
"Target": "_self",
"IsResource": NumberInt(2),
"Application": "BA"
},
{
"_id": ObjectId("1bd7b8445fa31256f77e4b9d"),
"ParentId": "5bd7b8445fa31256f77e4b9a",
"Name": "日志",
"Order": NumberInt(30),
"Icon": "fa fa-fa",
"Url": "info",
"Category": "0",
"Target": "_self",
"IsResource": NumberInt(2),
"Application": "BA"
},
{
"_id": ObjectId("5bd7b8445fa31256f77e4b9b"),
"ParentId": "0",

View File

@ -110,6 +110,8 @@ INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category, IsResourc
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category, IsResource) VALUES (@@identity - 2, '删除', 30, 'fa fa-fa', 'del', '0', 2);
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category) VALUES (0, '站内消息', 100, 'fa fa-envelope', '~/Admin/Messages', '0');
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category) VALUES (0, '任务管理', 110, 'fa fa fa-tasks', '~/Admin/Tasks', '0');
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category, IsResource) VALUES (@@identity, '暂停', 10, 'fa fa-fa', 'pause', '0', 2);
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category, IsResource) VALUES (@@identity - 1, '日志', 20, 'fa fa-fa', 'info', '0', 2);
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category) VALUES (0, '通知管理', 120, 'fa fa-bell', '~/Admin/Notifications', '0');
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category) VALUES (0, '系统日志', 130, 'fa fa-gears', '#', '0');
INSERT INTO Navigations (ParentId, Name, `Order`, Icon, Url, Category) VALUES (@@identity, '操作日志', 10, 'fa fa-edit', '~/Admin/Logs', '0');

9
db/MySQL/install.ps1 Normal file
View File

@ -0,0 +1,9 @@
# init mysql database
$startPath = $args[0]
if ($startPath -eq $null) {
$startPath = "Z:\src\Longbow\BootstrapAdmin\db\SqlServer"
}
mysql -e "drop database if exists BootstrapAdmin; create database BootstrapAdmin;" -uroot
mysql -hlocalhost -uroot -DBootstrapAdmin < $startPath\install.sql
mysql -hlocalhost -uroot -DBootstrapAdmin < $startPath\initData.sql

View File

@ -111,6 +111,8 @@ INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (currval('navigations_id_seq') - 3, '删除', 30, 'fa fa-fa', 'del', '0', 2);
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '站内消息', 100, 'fa fa-envelope', '~/Admin/Messages', '0');
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '任务管理', 110, 'fa fa fa-tasks', '~/Admin/Tasks', '0');
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (currval('navigations_id_seq') - 1, '暂停', 10, 'fa fa-fa', 'pause', '0', 2);
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (currval('navigations_id_seq') - 2, '日志', 20, 'fa fa-fa', 'info', '0', 2);
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '通知管理', 120, 'fa fa-bell', '~/Admin/Notifications', '0');
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (0, '系统日志', 130, 'fa fa-gears', '#', '0');
INSERT INTO Navigations (ParentId, Name, "order", Icon, Url, Category) VALUES (currval('navigations_id_seq') - 1, 0, '操作日志', 10, 'fa fa-edit', '~/Admin/Logs', '0');

View File

@ -110,6 +110,8 @@ INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (last_insert_rowid() - 2, '删除', 30, 'fa fa-fa', 'del', '0', 2);
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '站内消息', 100, 'fa fa-envelope', '~/Admin/Messages', '0');
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '任务管理', 110, 'fa fa fa-tasks', '~/Admin/Tasks', '0');
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (last_insert_rowid(), '暂停', 10, 'fa fa-fa', 'pause', '0', 2);
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (last_insert_rowid() - 1, '日志', 20, 'fa fa-fa', 'info', '0', 2);
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '通知管理', 120, 'fa fa-bell', '~/Admin/Notifications', '0');
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, '系统日志', 130, 'fa fa-gears', '#', '0');
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (last_insert_rowid(), '操作日志', 10, 'fa fa-edit', '~/Admin/Logs', '0');

View File

@ -113,6 +113,8 @@ INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (@@Identity - 2, N'删除', 30, 'fa fa-fa', 'del', '0', 2);
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, N'站内消息', 100, N'fa fa-envelope', N'~/Admin/Messages', N'0')
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, N'任务管理', 110, N'fa fa fa-tasks', N'~/Admin/Tasks', N'0')
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (@@Identity, N'暂停', 10, 'fa fa-fa', 'pause', '0', 2);
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category], IsResource) VALUES (@@Identity - 1, N'日志', 20, 'fa fa-fa', 'info', '0', 2);
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, N'通知管理', 120, N'fa fa-bell', N'~/Admin/Notifications', N'0')
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (0, N'日志管理', 130, N'fa fa-gears', N'#', N'0')
INSERT INTO [Navigations] ([ParentId], [Name], [Order], [Icon], [Url], [Category]) VALUES (@@Identity, N'操作日志', 10, N'fa fa-edit', N'~/Admin/Logs', N'0')

13
src/Directory.Build.props Normal file
View File

@ -0,0 +1,13 @@
<Project>
<Import Project="..\Directory.Build.props" />
<PropertyGroup>
<PackageProjectUrl>https://gitee.com/LongbowGroup/$(MsBuildProjectName)</PackageProjectUrl>
<RepositoryUrl>https://gitee.com/LongbowGroup/$(MsBuildProjectName).git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Nullable>enable</Nullable>
<DocumentationFile>$(MSBuildProjectName).xml</DocumentationFile>
</PropertyGroup>
</Project>

View File

@ -1,14 +1,6 @@
<Router AppAssembly="@typeof(Program).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<NotAuthorizedHandler></NotAuthorizedHandler>
</NotAuthorized>
<Authorizing>
<h1>Authentication in progress</h1>
<p>Only visible while authentication is in progress.</p>
</Authorizing>
</AuthorizeRouteView>
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<CascadingAuthenticationState>

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Longbow.Logging" Version="3.0.2-beta1" />
<PackageReference Include="Longbow.Logging" Version="3.0.3-beta1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Versioning" Version="4.0.0" />
<PackageReference Include="Sentry.AspNetCore" Version="2.0.0-beta6" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc4" />

View File

@ -73,7 +73,7 @@ namespace Bootstrap.Admin.Controllers
/// <param name="appId"></param>
/// <returns></returns>
[HttpGet]
public ActionResult Login([FromQuery]string appId = null)
public ActionResult Login([FromQuery]string? appId = null)
{
if (DictHelper.RetrieveSystemModel())
{
@ -111,11 +111,14 @@ namespace Bootstrap.Admin.Controllers
Description = "手机用户",
App = provider.Options.App
};
UserHelper.Save(user);
if (UserHelper.Save(user) && !string.IsNullOrEmpty(user.Id))
{
// 根据配置文件设置默认角色
var roles = RoleHelper.Retrieves().Where(r => provider.Options.Roles.Any(rl => rl.Equals(r.RoleName, StringComparison.OrdinalIgnoreCase))).Select(r => r.Id);
#pragma warning disable CS8620 // 由于引用类型的可为 null 性差异,实参不能用于形参。
RoleHelper.SaveByUserId(user.Id, roles);
#pragma warning restore CS8620 // 由于引用类型的可为 null 性差异,实参不能用于形参。
}
}
}
return auth ? await SignInAsync(phone, true, MobileSchema) : RedirectLogin();

View File

@ -8,7 +8,7 @@ using System.Linq;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 网站分析控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -16,11 +16,11 @@ namespace Bootstrap.Admin.Controllers.Api
public class AnalyseController : ControllerBase
{
/// <summary>
///
/// 通过 logType 查询分析数据接口
/// </summary>
/// <returns></returns>
[HttpGet()]
public ActionResult<AnalyseData> Get([FromQuery]string logType = "")
public ActionResult<AnalyseData> Get([FromQuery]string logType)
{
var ret = new AnalyseData();
if (logType.Equals("LoginUsers", StringComparison.OrdinalIgnoreCase))
@ -60,19 +60,19 @@ namespace Bootstrap.Admin.Controllers.Api
}
/// <summary>
///
/// 分析数据实体类
/// </summary>
public class AnalyseData
{
/// <summary>
///
/// 获得/设置 折线数据集合
/// </summary>
public IEnumerable<string> Polylines { get; set; }
public IEnumerable<string> Polylines { get; set; } = new string[0];
/// <summary>
///
/// 获得 数据集合
/// </summary>
public List<KeyValuePair<string, string>> Datas { get; set; } = new List<KeyValuePair<string, string>>();
public List<KeyValuePair<string, string>> Datas { get; } = new List<KeyValuePair<string, string>>();
}
}
}

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 应用程序控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -14,7 +14,7 @@ namespace Bootstrap.Admin.Controllers.Api
public class AppsController : ControllerBase
{
/// <summary>
///
/// 通过角色ID获取其授权的所有应用程序集合
/// </summary>
/// <param name="id"></param>
/// <returns></returns>

View File

@ -26,7 +26,7 @@ namespace Bootstrap.Admin.Controllers.Api
}
/// <summary>
///
/// 获取所有菜单数据
/// </summary>
/// <returns></returns>
[HttpGet]
@ -36,7 +36,7 @@ namespace Bootstrap.Admin.Controllers.Api
}
/// <summary>
///
/// 获取所有父级菜单数据
/// </summary>
/// <returns></returns>
[HttpGet]
@ -44,5 +44,25 @@ namespace Bootstrap.Admin.Controllers.Api
{
return MenuHelper.RetrieveMenus(User.Identity.Name).Where(m => m.Menus.Count() > 0).OrderBy(m => m.Name).Select(m => m.Name);
}
/// <summary>
/// 通过指定菜单检查子菜单是否有子菜单
/// </summary>
/// <returns></returns>
[HttpGet("{id}")]
public bool ValidateMenuBySubMenu(string id)
{
return !MenuHelper.RetrieveAllMenus(User.Identity.Name).Where(m => m.ParentId == id).Any();
}
/// <summary>
/// 通过指定菜单检查父级菜单是否为菜单类型 资源与按钮返回 false
/// </summary>
/// <returns></returns>
[HttpGet("{id}")]
public bool ValidateParentMenuById(string id)
{
return MenuHelper.RetrieveAllMenus(User.Identity.Name).FirstOrDefault(m => m.Id == id)?.IsResource == 0;
}
}
}

View File

@ -10,7 +10,7 @@ using System.Collections.Generic;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 字典表维护控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -18,7 +18,7 @@ namespace Bootstrap.Admin.Controllers.Api
public class DictsController : ControllerBase
{
/// <summary>
///
/// 获取所有字典表数据方法
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
@ -28,7 +28,7 @@ namespace Bootstrap.Admin.Controllers.Api
return value.RetrieveData();
}
/// <summary>
///
/// 保存字典方法
/// </summary>
/// <param name="value"></param>
[HttpPost]
@ -38,7 +38,7 @@ namespace Bootstrap.Admin.Controllers.Api
return DictHelper.Save(value);
}
/// <summary>
///
/// 删除字典项方法
/// </summary>
/// <param name="value"></param>
[HttpDelete]

View File

@ -57,12 +57,14 @@ namespace Bootstrap.Admin.Controllers.Api
var filePath = Path.Combine(AppContext.BaseDirectory, "Error");
var logName = $"{Path.Combine(filePath, exceptionFile.FileName)}.log";
if (!System.IO.File.Exists(logName)) return new JsonResult("无此日志文件");
StringBuilder sb = new StringBuilder();
using (StreamReader reader = new StreamReader(logName))
var sb = new StringBuilder();
using (var reader = new StreamReader(logName))
{
while (!reader.EndOfStream)
{
var line = reader.ReadLine().Replace("<", "&lt;").Replace(">", "&gt;");
var line = reader.ReadLine()?.Replace("<", "&lt;").Replace(">", "&gt;");
if (!string.IsNullOrEmpty(line))
{
if (line == "General Information ") sb.AppendFormat("<h4><b>{0}</b></h4>", line);
else if (line.StartsWith("TimeStamp:")) sb.AppendFormat("<div class='logTs'>{0}</div>", line);
else if (line.EndsWith("Exception Information")) sb.AppendFormat("<div class='logExcep'>{0}</div>", line);
@ -73,18 +75,19 @@ namespace Bootstrap.Admin.Controllers.Api
else sb.AppendFormat("{0}<br>", line);
}
}
}
return new JsonResult(sb.ToString());
}
/// <summary>
///
/// 查询服务器端日志文件参数类
/// </summary>
public class ExceptionFileQuery
{
/// <summary>
///
/// 获取/设置 文件名称
/// </summary>
public string FileName { get; set; }
public string FileName { get; set; } = "";
}
}
}

View File

@ -27,7 +27,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <param name="color"></param>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult> Issues([FromServices]GiteeHttpClient client, [FromQuery]string userName = "LongbowEnterprise", [FromQuery]string repoName = "BootstrapAdmin", [FromQuery]string label = "custom badge", [FromQuery]string color = "orange")
public async Task<ActionResult> Issues([FromServices]GiteeHttpClient client, [FromQuery]string? userName = "LongbowEnterprise", [FromQuery]string? repoName = "BootstrapAdmin", [FromQuery]string? label = "custom badge", [FromQuery]string? color = "orange")
{
var ret = await GetJsonAsync(() => client.HttpClient.GetStringAsync($"https://gitee.com/{userName}/{repoName}/issues"), content =>
{
@ -50,7 +50,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <param name="color"></param>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult> Pulls([FromServices]GiteeHttpClient client, [FromQuery]string userName = "LongbowEnterprise", [FromQuery]string repoName = "BootstrapAdmin", [FromQuery]string label = "custom badge", [FromQuery]string color = "orange")
public async Task<ActionResult> Pulls([FromServices]GiteeHttpClient client, [FromQuery]string? userName = "LongbowEnterprise", [FromQuery]string? repoName = "BootstrapAdmin", [FromQuery]string? label = "custom badge", [FromQuery]string? color = "orange")
{
var ret = await GetJsonAsync(() => client.HttpClient.GetStringAsync($"https://gitee.com/{userName}/{repoName}/pulls"), content =>
{
@ -72,7 +72,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <param name="color"></param>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult> Releases([FromServices]GiteeHttpClient client, [FromQuery]string userName = "LongbowEnterprise", [FromQuery]string repoName = "BootstrapAdmin", [FromQuery]string label = "custom badge", [FromQuery]string color = "orange")
public async Task<ActionResult> Releases([FromServices]GiteeHttpClient client, [FromQuery]string? userName = "LongbowEnterprise", [FromQuery]string? repoName = "BootstrapAdmin", [FromQuery]string? label = "custom badge", [FromQuery]string? color = "orange")
{
var ret = await GetJsonAsync(() => client.HttpClient.GetStringAsync($"https://gitee.com/{userName}/{repoName}/releases"), content =>
{
@ -93,7 +93,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <param name="color"></param>
/// <returns></returns>
[HttpGet]
public async Task<ActionResult> Builds([FromServices]GiteeHttpClient client, [FromQuery]string userName = "ArgoZhang", [FromQuery]string projName = "bootstrapadmin", [FromQuery]string branchName = "master", [FromQuery]string label = "custom badge", [FromQuery]string color = "orange")
public async Task<ActionResult> Builds([FromServices]GiteeHttpClient client, [FromQuery]string? userName = "ArgoZhang", [FromQuery]string? projName = "bootstrapadmin", [FromQuery]string? branchName = "master", [FromQuery]string? label = "custom badge", [FromQuery]string? color = "orange")
{
var ret = await GetJsonAsync(() => client.HttpClient.GetAsJsonAsync<AppveyorBuildResult>($"https://ci.appveyor.com/api/projects/{userName}/{projName}/branch/{branchName}", null, new CancellationTokenSource(10000).Token), content =>
{
@ -126,7 +126,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <summary>
/// Appveyor 编译版本实例
/// </summary>
public Build Build { get; set; }
public Build Build { get; set; } = new Build();
}
private class Build
@ -134,7 +134,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <summary>
/// Build 版本信息
/// </summary>
public string Version { get; set; }
public string Version { get; set; } = "";
}
}
}

View File

@ -5,12 +5,11 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Collections.Generic;
using System.Linq;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 部门维护控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -18,7 +17,7 @@ namespace Bootstrap.Admin.Controllers.Api
public class GroupsController : ControllerBase
{
/// <summary>
///
/// 部门数据查询方法
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
@ -29,18 +28,7 @@ namespace Bootstrap.Admin.Controllers.Api
}
/// <summary>
///
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet("{id}")]
public Group Get(string id)
{
return GroupHelper.Retrieves().FirstOrDefault(t => t.Id == id);
}
/// <summary>
///
/// 保存部门方法
/// </summary>
/// <param name="value"></param>
[HttpPost]
@ -51,7 +39,7 @@ namespace Bootstrap.Admin.Controllers.Api
}
/// <summary>
///
/// 删除部门方法
/// </summary>
/// <param name="value"></param>
[HttpDelete]
@ -64,7 +52,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <summary>
/// 获取部门授权
/// </summary>
/// <param name="id"></param>
/// <param name="id">用户ID或者角色ID</param>
/// <param name="type"></param>
/// <returns></returns>
[HttpPost("{id}")]

View File

@ -3,7 +3,10 @@ using Bootstrap.Security;
using Bootstrap.Security.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
namespace Bootstrap.Admin.Controllers
{
@ -50,7 +53,7 @@ namespace Bootstrap.Admin.Controllers
/// </summary>
/// <returns></returns>
[HttpPost]
public BootstrapUser RetrieveUserByUserName([FromBody]string userName)
public BootstrapUser? RetrieveUserByUserName([FromBody]string userName)
{
return UserHelper.RetrieveUserByUserName(userName);
}
@ -63,7 +66,28 @@ namespace Bootstrap.Admin.Controllers
[HttpPost]
public IEnumerable<BootstrapMenu> RetrieveAppMenus([FromBody]AppMenuOption args)
{
if (string.IsNullOrEmpty(args.AppId) || string.IsNullOrEmpty(args.UserName)) return new BootstrapMenu[0];
return MenuHelper.RetrieveAppMenus(args.AppId, args.UserName, args.Url);
}
/// <summary>
/// 发送健康检查结果
/// </summary>
/// <param name="httpClient"></param>
/// <param name="config"></param>
/// <param name="message"></param>
/// <returns></returns>
[HttpPost]
public async Task<bool> Healths([FromServices]GiteeHttpClient httpClient, [FromServices]IConfiguration config, [FromBody]string message)
{
var ret = false;
var url = config.GetValue("HealthsCloudUrl", "");
if (!string.IsNullOrEmpty(url))
{
try { await httpClient.HttpClient.PostAsJsonAsync(url, message); ret = true; }
catch { }
}
return ret;
}
}
}

View File

@ -32,7 +32,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <param name="user"></param>
/// <returns></returns>
[HttpPost]
public string Post([FromBody]User user)
public string? Post([FromBody]User user)
{
var token = string.Empty;
string userName = user.UserName;
@ -59,7 +59,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// </summary>
/// <returns></returns>
[HttpOptions]
public string Options()
public string? Options()
{
return null;
}

View File

@ -42,7 +42,7 @@ namespace Bootstrap.Admin.Controllers.Api
value.Browser = $"{agent.Browser?.Name} {agent.Browser?.Version}";
value.OS = $"{agent.OS?.Name} {agent.OS?.Version}";
value.City = ipLocator.Locate(value.Ip);
value.UserName = User.Identity.Name;
value.UserName = User.Identity.Name ?? string.Empty;
return LogHelper.Save(value);
}
}

View File

@ -7,7 +7,7 @@ using System.Linq;
namespace Bootstrap.Admin.Controllers
{
/// <summary>
///
/// 新用户注册控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -38,13 +38,17 @@ namespace Bootstrap.Admin.Controllers
public bool Put([FromBody]User value)
{
var ret = false;
var userName = User.Identity.Name;
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(value.Id))
{
if (value.UserStatus == UserStates.ApproveUser)
{
ret = UserHelper.Approve(value.Id, User.Identity.Name);
ret = UserHelper.Approve(value.Id, userName);
}
else if (value.UserStatus == UserStates.RejectUser)
{
ret = UserHelper.Reject(value.Id, User.Identity.Name);
ret = UserHelper.Reject(value.Id, userName);
}
}
return ret;
}

View File

@ -56,7 +56,7 @@ namespace Bootstrap.Admin.Controllers.Api
apps = apps.Take(6);
apps.AsParallel().ForAll(n =>
{
n.ExceptionType = n.ExceptionType.Split('.').Last();
n.ExceptionType = n.ExceptionType?.Split('.').Last();
var ts = DateTime.Now - n.LogTime;
if (ts.TotalMinutes < 5) n.Period = "刚刚";
else if (ts.Days > 0) n.Period = string.Format("{0}天", ts.Days);

View File

@ -72,7 +72,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <summary>
///
/// </summary>
public string Ip { get; set; }
public string? Ip { get; set; }
/// <summary>
///
@ -108,7 +108,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// </summary>
public void Reset()
{
if (dispatcher != null) dispatcher.Change(TimeSpan.FromSeconds(30), Timeout.InfiniteTimeSpan);
dispatcher.Change(TimeSpan.FromSeconds(30), Timeout.InfiniteTimeSpan);
}
#region Impletement IDispose
@ -123,7 +123,6 @@ namespace Bootstrap.Admin.Controllers.Api
if (dispatcher != null)
{
dispatcher.Dispose();
dispatcher = null;
}
}
}

View File

@ -11,7 +11,7 @@ using System.Threading.Tasks;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 个人中心控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -30,9 +30,6 @@ namespace Bootstrap.Admin.Controllers.Api
public JsonResult Post(string id, [FromServices]IWebHostEnvironment env, [FromForm]DeleteFileCollection files)
{
if (!id.Equals("Delete", StringComparison.OrdinalIgnoreCase)) return new JsonResult(new object());
var previewUrl = string.Empty;
long fileSize = 0;
var userName = User.Identity.Name;
var fileName = files.Key;
@ -41,10 +38,10 @@ namespace Bootstrap.Admin.Controllers.Api
fileName = "default.jpg";
var webSiteUrl = DictHelper.RetrieveIconFolderPath();
var filePath = Path.Combine(env.WebRootPath, webSiteUrl.Replace("~", string.Empty).Replace('/', Path.DirectorySeparatorChar).TrimStart(Path.DirectorySeparatorChar) + fileName);
fileSize = new FileInfo(filePath).Length;
var fileSize = new FileInfo(filePath).Length;
var iconName = $"{fileName}?v={DateTime.Now.Ticks}";
previewUrl = Url.Content($"{webSiteUrl}{iconName}");
UserHelper.SaveUserIconByName(userName, iconName);
var previewUrl = Url.Content($"{webSiteUrl}{iconName}");
if (!string.IsNullOrEmpty(userName)) UserHelper.SaveUserIconByName(userName, iconName);
return new JsonResult(new
{
@ -57,14 +54,14 @@ namespace Bootstrap.Admin.Controllers.Api
}
/// <summary>
///
/// 待删除文件集合类
/// </summary>
public class DeleteFileCollection
{
/// <summary>
///
/// 获得/设置 文件名称
/// </summary>
public string Key { get; set; }
public string Key { get; set; } = "";
}
/// <summary>
@ -96,7 +93,7 @@ namespace Bootstrap.Admin.Controllers.Api
}
var iconName = $"{fileName}?v={DateTime.Now.Ticks}";
previewUrl = Url.Content($"{webSiteUrl}{iconName}");
UserHelper.SaveUserIconByName(userName, iconName);
if (!string.IsNullOrEmpty(userName)) UserHelper.SaveUserIconByName(userName, iconName);
}
return new JsonResult(new
{
@ -109,7 +106,7 @@ namespace Bootstrap.Admin.Controllers.Api
}
/// <summary>
///
/// 个人中心操作方法 更改样式 更改显示名称 更改默认应用
/// </summary>
/// <returns></returns>
[HttpPut]

View File

@ -11,7 +11,7 @@ using System.Linq;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 角色维护控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -19,7 +19,7 @@ namespace Bootstrap.Admin.Controllers.Api
public class RolesController : ControllerBase
{
/// <summary>
///
/// 获取所有角色数据
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
@ -37,7 +37,7 @@ namespace Bootstrap.Admin.Controllers.Api
[HttpPost("{id}")]
public IEnumerable<object> Post(string id, [FromQuery]string type)
{
IEnumerable<Role> ret = new List<Role>();
IEnumerable<Role> ret = new Role[0];
switch (type)
{
case "user":
@ -53,7 +53,7 @@ namespace Bootstrap.Admin.Controllers.Api
return ret.Select(m => new { m.Id, m.Checked, m.RoleName, m.Description });
}
/// <summary>
/// 保存角色
/// 保存角色授权方法
/// </summary>
/// <param name="id">角色ID</param>
/// <param name="values">选中的ID集合</param>
@ -82,7 +82,7 @@ namespace Bootstrap.Admin.Controllers.Api
return ret;
}
/// <summary>
///
/// 保存角色方法
/// </summary>
/// <param name="value"></param>
[HttpPost]
@ -92,7 +92,7 @@ namespace Bootstrap.Admin.Controllers.Api
return RoleHelper.Save(value);
}
/// <summary>
///
/// 删除角色方法
/// </summary>
/// <param name="value"></param>
[HttpDelete]

View File

@ -9,7 +9,7 @@ using System.Collections.Generic;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 网站设置控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -17,7 +17,7 @@ namespace Bootstrap.Admin.Controllers.Api
public class SettingsController : ControllerBase
{
/// <summary>
///
/// 保存网站设置方法
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
@ -26,7 +26,7 @@ namespace Bootstrap.Admin.Controllers.Api
public bool Post([FromBody]BootstrapDict value) => DictHelper.SaveSettings(value);
/// <summary>
///
/// 获取网站缓存站点集合
/// </summary>
[HttpGet]
public IEnumerable<ICacheCorsItem> Get() => CacheManager.CorsSites;

View File

@ -1,4 +1,5 @@
using Longbow.Tasks;
using Bootstrap.DataAccess;
using Longbow.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
@ -7,7 +8,7 @@ using System.Linq;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 任务管理控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -15,7 +16,7 @@ namespace Bootstrap.Admin.Controllers.Api
public class TasksController : ControllerBase
{
/// <summary>
///
/// 获取所有任务数据
/// </summary>
/// <returns></returns>
[HttpGet]
@ -23,5 +24,25 @@ namespace Bootstrap.Admin.Controllers.Api
{
return TaskServicesManager.ToList().Select(s => new { s.Name, Status = s.Status.ToString(), s.LastRuntime, s.CreatedTime, s.NextRuntime, LastRunResult = s.Triggers.First().LastResult.ToString(), TriggerExpression = s.Triggers.FirstOrDefault().ToString() }).OrderBy(s => s.Name);
}
/// <summary>
/// 任务相关操作
/// </summary>
/// <param name="scheName">调度名称</param>
/// <param name="operType">操作方式 pause run</param>
[HttpPut("{scheName}")]
public bool Put(string scheName, [FromQuery]string operType)
{
var sche = TaskServicesManager.Get(scheName);
if (sche != null) sche.Status = operType == "pause" ? SchedulerStatus.Disabled : SchedulerStatus.Running;
// SQL 日志任务特殊处理
if (scheName == "SQL日志")
{
if (operType == "pause") LogHelper.Pause();
else LogHelper.Run();
}
return true;
}
}
}

View File

@ -8,7 +8,7 @@ using System.Threading.Tasks;
namespace Bootstrap.Admin.Controllers.Api
{
/// <summary>
///
/// 任务日志控制器
/// </summary>
[Route("api/[controller]")]
[Authorize]
@ -25,9 +25,14 @@ namespace Bootstrap.Admin.Controllers.Api
public ActionResult Get([FromQuery]string name, [FromServices]IHubContext<TaskLogHub> hub)
{
var sche = TaskServicesManager.Get(name);
var ret = false;
if (sche != null)
{
sche.Triggers.First().PulseCallback = t => SendTaskLog(sche, name, hub);
SendTaskLog(sche, name, hub);
return Ok(true);
ret = true;
}
return Ok(ret);
}
private Task SendTaskLog(IScheduler sche, string name, IHubContext<TaskLogHub> hub)

View File

@ -38,7 +38,7 @@ namespace Bootstrap.Admin.Controllers.Api
[HttpPost("{id}")]
public IEnumerable<object> Post(string id, [FromQuery]string type)
{
IEnumerable<object> ret = null;
IEnumerable<object> ret = new string[0];
switch (type)
{
case "role":
@ -68,7 +68,7 @@ namespace Bootstrap.Admin.Controllers.Api
[ButtonAuthorize(Url = "~/Admin/Users", Auth = "add,edit")]
public bool Post([FromBody]User value)
{
var ret = false;
bool ret;
if (string.IsNullOrEmpty(value.Id))
{
value.Description = string.Format("管理员{0}创建用户", User.Identity.Name);
@ -124,7 +124,7 @@ namespace Bootstrap.Admin.Controllers.Api
/// <returns></returns>
[AllowAnonymous]
[HttpOptions]
public string Options()
public string? Options()
{
return null;
}

View File

@ -22,7 +22,7 @@ namespace Bootstrap.Admin.Controllers
var model = new HeaderBarModel(User.Identity.Name);
var homeUrl = DictHelper.RetrieveHomeUrl(model.AppId);
var useBlazor = configuration.GetValue("UseBlazor", false);
return useBlazor ? Redirect("~/Admin/Home") : homeUrl.Equals("~/Home/Index", System.StringComparison.OrdinalIgnoreCase) ? (IActionResult)View(model) : Redirect(homeUrl);
return useBlazor ? Redirect("~/Pages") : homeUrl.Equals("~/Home/Index", System.StringComparison.OrdinalIgnoreCase) ? (IActionResult)View(model) : Redirect(homeUrl);
}
/// <summary>

View File

@ -2,9 +2,9 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
using System.Linq;
using System;
namespace Microsoft.AspNetCore.Builder
{
@ -25,6 +25,12 @@ namespace Microsoft.AspNetCore.Builder
app.Use(async (context, next) =>
{
if (!_init)
{
// 优化性能
var config = context.RequestServices.GetRequiredService<IConfiguration>();
var autoGenerate = config.GetValue("AutoGenerateDatabase", false);
if (!autoGenerate) _init = true;
if (autoGenerate)
{
// 阻止所有线程继续往下运行,等待数据库检查
lock (_locker)
@ -32,11 +38,10 @@ namespace Microsoft.AspNetCore.Builder
if (!_init)
{
// 数据检查
var config = context.RequestServices.GetRequiredService<IConfiguration>();
var dbSection = config.GetSection("DB").GetChildren().FirstOrDefault(c => c.GetValue("Enabled", false));
if (dbSection != null)
{
var folder = dbSection[SqlFolderKey]?.ReplaceOSPlatformPath();
var folder = dbSection.GetValue(SqlFolderKey, "").ReplaceOSPlatformPath();
if (!string.IsNullOrEmpty(folder))
{
// 判断文件夹是否存在
@ -46,7 +51,7 @@ namespace Microsoft.AspNetCore.Builder
{
try
{
AutoDbHelper.CheckDB(fullFolder);
AutoDbHelper.EnsureCreated(fullFolder);
}
catch { }
}
@ -56,6 +61,7 @@ namespace Microsoft.AspNetCore.Builder
_init = true;
}
}
}
await next();
});
return app;

View File

@ -0,0 +1,88 @@
using Longbow.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Options;
using System;
using System.Net.Http;
namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// 邮件日志扩展方法
/// </summary>
public static class CloudLoggerExtensions
{
/// <summary>
/// 注册邮件日志方法
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static ILoggingBuilder AddCloudLogger(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<IConfigureOptions<CloudLoggerOption>, LoggerProviderConfigureOptions<CloudLoggerOption, CloudLoggerProvider>>();
builder.Services.AddSingleton<IOptionsChangeTokenSource<CloudLoggerOption>, LoggerProviderOptionsChangeTokenSource<CloudLoggerOption, CloudLoggerProvider>>();
builder.Services.AddSingleton<ILoggerProvider, CloudLoggerProvider>();
return builder;
}
}
/// <summary>
/// 云日志提供类
/// </summary>
[ProviderAlias("Cloud")]
public class CloudLoggerProvider : LoggerProvider
{
private readonly HttpClient httpClient;
private readonly IDisposable optionsReloadToken;
private CloudLoggerOption option;
/// <summary>
/// 构造函数
/// </summary>
public CloudLoggerProvider(IOptionsMonitor<CloudLoggerOption> options) : base(null, new Func<string, LogLevel, bool>((name, logLevel) => logLevel >= LogLevel.Error))
{
optionsReloadToken = options.OnChange(op => option = op);
option = options.CurrentValue;
httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(10)
};
httpClient.DefaultRequestHeaders.Connection.Add("keep-alive");
LogCallback = new Action<string>(async message =>
{
if (!string.IsNullOrEmpty(option.Url))
{
try { await httpClient.PostAsJsonAsync(option.Url, message).ConfigureAwait(false); }
catch { }
}
});
}
/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
httpClient.Dispose();
optionsReloadToken.Dispose();
}
}
}
/// <summary>
/// 云日志配置类
/// </summary>
public class CloudLoggerOption
{
/// <summary>
/// 获得/设置 云日志地址
/// </summary>
public string Url { get; set; } = "";
}
}

View File

@ -18,7 +18,7 @@ namespace Bootstrap.Admin.HealthChecks
{
private readonly IConfiguration _configuration;
private readonly IHttpContextAccessor _httpContextAccessor;
private static readonly Func<IConfiguration, string, string> ConnectionStringResolve = (c, name) => string.IsNullOrEmpty(name)
private static readonly Func<IConfiguration, string, string?> ConnectionStringResolve = (c, name) => string.IsNullOrEmpty(name)
? c.GetSection("ConnectionStrings").GetChildren().FirstOrDefault()?.Value
: c.GetConnectionString(name);
@ -42,13 +42,13 @@ namespace Bootstrap.Admin.HealthChecks
public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
var db = _configuration.GetSection("DB").GetChildren()
.Select(config => new
.Select(config => new DbOption()
{
Enabled = bool.TryParse(config["Enabled"], out var en) ? en : false,
ProviderName = config["ProviderName"],
Widget = config["Widget"],
ConnectionString = ConnectionStringResolve(config.GetSection("ConnectionStrings").Exists() ? config : _configuration, string.Empty)
}).FirstOrDefault(i => i.Enabled) ?? new
}).FirstOrDefault(i => i.Enabled) ?? new DbOption()
{
Enabled = true,
ProviderName = Longbow.Data.DatabaseProviderType.SqlServer.ToString(),
@ -64,12 +64,11 @@ namespace Bootstrap.Admin.HealthChecks
var roles = string.Empty;
var displayName = string.Empty;
var healths = false;
Exception error = null;
Exception? error = null;
try
{
DbContextManager.Exception = null;
var user = UserHelper.RetrieveUserByUserName(userName);
displayName = user?.DisplayName;
displayName = user?.DisplayName ?? string.Empty;
roles = string.Join(",", RoleHelper.RetrievesByUserName(userName) ?? new string[0]);
menusCount = MenuHelper.RetrieveMenusByUserName(userName)?.Count() ?? 0;
dictsCount = DictHelper.RetrieveDicts()?.Count() ?? 0;
@ -79,32 +78,41 @@ namespace Bootstrap.Admin.HealthChecks
{
error = ex;
}
var data = new Dictionary<string, object>()
var data = new Dictionary<string, object?>()
{
{ "ConnectionString", db.ConnectionString },
{ "Reference", DbContextManager.Create<Dict>()?.GetType().Assembly.FullName ?? db.Widget },
{ "DbType", db?.ProviderName },
{ "Dicts", dictsCount },
{ "LoginName", loginUser },
{ "LoginName", userName },
{ "DisplayName", displayName },
{ "Roles", roles },
{ "Navigations", menusCount }
};
if (string.IsNullOrEmpty(db.ConnectionString))
if (string.IsNullOrEmpty(db?.ConnectionString))
{
// 未启用连接字符串
data["ConnectionString"] = "未配置数据库连接字符串";
return Task.FromResult(HealthCheckResult.Unhealthy("Error", null, data));
}
if (error != null || DbContextManager.Exception != null)
if (DbContextManager.Exception != null) error = DbContextManager.Exception;
if (error != null)
{
data.Add("Exception", (DbContextManager.Exception ?? error).Message);
return Task.FromResult(HealthCheckResult.Unhealthy("Error", error ?? DbContextManager.Exception, data));
data.Add("Exception", error.Message);
return Task.FromResult(HealthCheckResult.Unhealthy("Error", error, data));
}
return healths ? Task.FromResult(HealthCheckResult.Healthy("Ok", data)) : Task.FromResult(HealthCheckResult.Degraded("Failed", null, data));
}
private class DbOption
{
public bool Enabled { get; set; }
public string? ProviderName { get; set; }
public string? Widget { get; set; }
public string? ConnectionString { get; set; }
}
}
}

View File

@ -41,7 +41,7 @@ namespace Bootstrap.Admin.HealthChecks
Task.WaitAll(urls.Select(url => Task.Run(async () =>
{
var sw = Stopwatch.StartNew();
Exception error = null;
Exception? error = null;
var result = await _client.HttpClient.GetAsJsonAsync<object>($"/api/Gitee/{url}", ex => error = ex, cancellationToken);
sw.Stop();
data.Add(url, error == null ? $"{result} Elapsed: {sw.Elapsed}" : $"{result} Elapsed: {sw.Elapsed} Exception: {error}");

View File

@ -12,7 +12,7 @@ namespace Bootstrap.Admin.Models
/// 默认构造函数
/// </summary>
/// <param name="appId"></param>
public AdminModel(string appId = null)
public AdminModel(string? appId = null)
{
if (string.IsNullOrEmpty(appId)) appId = BootstrapAppContext.AppId;

View File

@ -13,28 +13,28 @@
/// <summary>
///
/// </summary>
public string Title { get; set; }
public string Title { get; set; } = "";
/// <summary>
///
/// </summary>
public string Content { get; set; }
public string Content { get; set; } = "";
/// <summary>
///
/// </summary>
public string Image { get; set; }
public string Image { get; set; } = "";
/// <summary>
///
/// </summary>
public string Detail { get; set; }
public string Detail { get; set; } = "";
/// <summary>
///
/// </summary>
public string ReturnUrl { get; set; }
public string ReturnUrl { get; set; } = "";
/// <summary>
///

View File

@ -13,7 +13,7 @@ namespace Bootstrap.Admin.Models
/// 默认构造函数
/// </summary>
/// <param name="userName"></param>
public HeaderBarModel(string userName)
public HeaderBarModel(string? userName)
{
var user = UserHelper.RetrieveUserByUserName(userName);
if (user != null)
@ -42,31 +42,31 @@ namespace Bootstrap.Admin.Models
/// <summary>
/// 获得 当前用户登录名
/// </summary>
public string UserName { get; }
public string UserName { get; } = "";
/// <summary>
/// 获得 当前用户显示名称
/// </summary>
public string DisplayName { get; }
public string DisplayName { get; } = "";
/// <summary>
/// 获得 用户头像地址
/// </summary>
public string Icon { get; }
public string Icon { get; } = "";
/// <summary>
/// 获取 个人网站样式
/// </summary>
public string Css { get; }
public string Css { get; } = "";
/// <summary>
/// 获得 当前设置的默认应用
/// </summary>
public string AppId { get; }
public string AppId { get; } = "";
/// <summary>
/// 获得 当前样式
/// </summary>
public string ActiveCss { get; }
public string ActiveCss { get; } = "";
}
}

View File

@ -9,7 +9,7 @@
/// 构造函数
/// </summary>
/// <param name="userName"></param>
public LockModel(string userName) : base(userName)
public LockModel(string? userName) : base(userName)
{
}
@ -17,11 +17,11 @@
/// <summary>
/// 获得/设置 返回路径
/// </summary>
public string ReturnUrl { get; set; }
public string? ReturnUrl { get; set; }
/// <summary>
/// 获得/设置 认证方式 Cookie Mobile Gitee GitHub
/// </summary>
public string AuthenticationType { get; set; }
public string? AuthenticationType { get; set; }
}
}

View File

@ -11,7 +11,7 @@ namespace Bootstrap.Admin.Models
/// 默认构造函数
/// </summary>
/// <param name="appId"></param>
public LoginModel(string appId = null) : base(appId)
public LoginModel(string? appId = null) : base(appId)
{
ImageLibUrl = DictHelper.RetrieveImagesLibUrl();
}

View File

@ -26,7 +26,7 @@ namespace Bootstrap.Admin.Models
/// </summary>
/// <param name="userName"></param>
/// <param name="activeUrl"></param>
public NavigatorBarModel(string userName, string activeUrl = "~/Admin/Index") : base(userName)
public NavigatorBarModel(string? userName, string activeUrl = "~/Admin/Index") : base(userName)
{
Navigations = MenuHelper.RetrieveSystemMenus(UserName, activeUrl);
var authApps = AppHelper.RetrievesByUserName(userName);

View File

@ -18,7 +18,7 @@ namespace Bootstrap.Admin.Models
/// <summary>
/// 获得 头像文件名称
/// </summary>
public string FileName { get; }
public string FileName { get; } = "";
/// <summary>
/// 获得 是否为第三方用户

View File

@ -1,4 +1,5 @@
@page "/Admin/Home"
@page "/Pages/Admin/Index"
@page "/Pages"
<h3>Home</h3>

View File

@ -0,0 +1,6 @@
@page "/Pages/Admin/Roles"
<h3>Roles</h3>
@code {
}

View File

@ -0,0 +1,6 @@
@page "/Pages/Admin/Users"
<h3>Users</h3>
@code {
}

View File

@ -0,0 +1,22 @@
namespace Bootstrap.Admin.Pages
{
/// <summary>
/// Url 地址辅助操作类
/// </summary>
public static class UrlHelper
{
/// <summary>
/// 转换为 Blazor 地址
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static string ToBlazorLink(this string url) => url.TrimStart('~');
/// <summary>
/// 转化为 Blazor 菜单地址
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static string ToBlazorMenuUrl(this string url) => url.Replace("~", "/Pages");
}
}

View File

@ -1,4 +1,4 @@
@page "/"
@page "/Pages"
@namespace Bootstrap.Admin.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -13,15 +13,18 @@ namespace Bootstrap.Admin.Query
/// <summary>
/// 字典分项
/// </summary>
public string Category { get; set; }
public string? Category { get; set; }
/// <summary>
/// 字典名称
/// </summary>
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
/// 字典种类
/// </summary>
public string Define { get; set; }
public string? Define { get; set; }
/// <summary>
/// 字典表查询
/// </summary>
@ -34,15 +37,15 @@ namespace Bootstrap.Admin.Query
var data = DictHelper.RetrieveDicts();
if (!string.IsNullOrEmpty(Category))
{
data = data.Where(t => t.Category.Contains(Category));
data = data.Where(t => t.Category?.Contains(Category) ?? false);
}
if (!string.IsNullOrEmpty(Name))
{
data = data.Where(t => t.Name.Contains(Name));
data = data.Where(t => t.Name?.Contains(Name) ?? false);
}
if (!string.IsNullOrEmpty(Define))
{
data = data.Where(t => t.Define.ToString() == Define);
data = data.Where(t => t.Define.ToString() == (Define ?? ""));
}
var ret = new QueryData<BootstrapDict>();
ret.total = data.Count();

View File

@ -6,20 +6,22 @@ using System.Linq;
namespace Bootstrap.Admin.Query
{
/// <summary>
///
/// 程序异常查询条件类
/// </summary>
public class QueryExceptionOption : PaginationOption
{
/// <summary>
///
/// 获得/设置 开始时间
/// </summary>
public DateTime? StartTime { get; set; }
/// <summary>
///
/// 获得/设置 结束时间
/// </summary>
public DateTime? EndTime { get; set; }
/// <summary>
///
/// 查询方法
/// </summary>
/// <returns></returns>
public QueryData<object> Retrieves()

View File

@ -5,20 +5,22 @@ using System.Linq;
namespace Bootstrap.Admin.Query
{
/// <summary>
///
/// 部门查询条件类
/// </summary>
public class QueryGroupOption : PaginationOption
{
/// <summary>
///
/// 获得/设置 部门名称
/// </summary>
public string GroupName { get; set; }
public string? GroupName { get; set; }
/// <summary>
///
/// 获得/设置 部门描述
/// </summary>
public string Description { get; set; }
public string? Description { get; set; }
/// <summary>
///
/// 部门查询数据方法
/// </summary>
/// <returns></returns>
public QueryData<object> RetrieveData()
@ -27,11 +29,11 @@ namespace Bootstrap.Admin.Query
var data = GroupHelper.Retrieves();
if (!string.IsNullOrEmpty(GroupName))
{
data = data.Where(t => t.GroupName.Contains(GroupName));
data = data.Where(t => t.GroupName?.Contains(GroupName) ?? false);
}
if (!string.IsNullOrEmpty(Description))
{
data = data.Where(t => t.Description.Contains(Description));
data = data.Where(t => t.Description?.Contains(Description) ?? false);
}
var ret = new QueryData<object>();
ret.total = data.Count();

View File

@ -5,27 +5,27 @@ using System;
namespace Bootstrap.Admin.Query
{
/// <summary>
///
/// 操作日志查询条件类
/// </summary>
public class QueryLogOption : PaginationOption
{
/// <summary>
///
/// 获得/设置 操作类型
/// </summary>
public string OperateType { get; set; }
public string? OperateType { get; set; }
/// <summary>
///
/// 获得/设置 开始时间
/// </summary>
public DateTime? OperateTimeStart { get; set; }
/// <summary>
///
/// 获得/设置 结束时间
/// </summary>
public DateTime? OperateTimeEnd { get; set; }
/// <summary>
///
/// 获得/设置 获取查询分页数据
/// </summary>
/// <returns></returns>
public QueryData<Log> RetrieveData()

View File

@ -22,7 +22,7 @@ namespace Bootstrap.Admin.Query
/// <summary>
/// 登录IP地址
/// </summary>
public string LoginIP { get; set; }
public string? LoginIP { get; set; }
/// <summary>
///
@ -30,8 +30,6 @@ namespace Bootstrap.Admin.Query
/// <returns></returns>
public QueryData<LoginUser> RetrieveData()
{
if (string.IsNullOrEmpty(Order)) Order = "desc";
if (string.IsNullOrEmpty(Sort)) Sort = "LoginTime";
var data = LoginHelper.RetrievePages(this, StartTime, EndTime, LoginIP);
return new QueryData<LoginUser>
{

View File

@ -13,34 +13,34 @@ namespace Bootstrap.Admin.Query
/// <summary>
///
/// </summary>
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
///
/// </summary>
public string ParentName { get; set; }
public string? ParentName { get; set; }
/// <summary>
///
/// </summary>
public string Category { get; set; }
public string? Category { get; set; }
/// <summary>
///
/// </summary>
public string IsResource { get; set; }
public string? IsResource { get; set; }
/// <summary>
///
/// </summary>
public string AppId { get; set; }
public string? AppId { get; set; }
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public QueryData<object> RetrieveData(string userName)
public QueryData<object> RetrieveData(string? userName)
{
var data = MenuHelper.RetrieveMenusByUserName(userName);
if (!string.IsNullOrEmpty(ParentName))

View File

@ -5,20 +5,22 @@ using System.Linq;
namespace Bootstrap.Admin.Query
{
/// <summary>
///
/// 角色查询条件类
/// </summary>
public class QueryRoleOption : PaginationOption
{
/// <summary>
///
/// 角色名称
/// </summary>
public string RoleName { get; set; }
public string? RoleName { get; set; }
/// <summary>
///
/// 角色描述
/// </summary>
public string Description { get; set; }
public string? Description { get; set; }
/// <summary>
///
/// 角色数据
/// </summary>
/// <returns></returns>
public QueryData<object> RetrieveData()

View File

@ -12,7 +12,7 @@ namespace Bootstrap.Admin.Query
/// <summary>
/// 获得/设置 用户登录名
/// </summary>
public string UserName { get; set; }
public string? UserName { get; set; }
/// <summary>
/// 获得/设置 开始时间
@ -30,7 +30,6 @@ namespace Bootstrap.Admin.Query
/// <returns></returns>
public QueryData<DBLog> RetrieveData()
{
if (string.IsNullOrEmpty(Order)) Order = "LogTime";
var data = LogHelper.RetrieveDBLogs(this, OperateTimeStart, OperateTimeEnd, UserName);
var ret = new QueryData<DBLog>();
ret.total = data.TotalItems;

View File

@ -22,7 +22,7 @@ namespace Bootstrap.Admin.Query
/// <summary>
/// 请求IP地址
/// </summary>
public string AccessIP { get; set; }
public string? AccessIP { get; set; }
/// <summary>
///

View File

@ -5,20 +5,22 @@ using System.Linq;
namespace Bootstrap.Admin.Query
{
/// <summary>
///
/// 用户维护查询条件类
/// </summary>
public class QueryUserOption : PaginationOption
{
/// <summary>
///
/// 获得/设置 用户登录名称
/// </summary>
public string Name { get; set; }
public string? Name { get; set; }
/// <summary>
///
/// 获得/设置 用户显示名称
/// </summary>
public string DisplayName { get; set; }
public string? DisplayName { get; set; }
/// <summary>
///
/// 获取用户分页数据
/// </summary>
/// <returns></returns>
public QueryData<object> RetrieveData()

View File

@ -1,16 +1,18 @@
<footer class="position-fixed">
<div><span id="websiteFooter">@Model.Footer</span><span asp-condition="@Model.IsDemo">(演示系统)</span></div>
@inject IConfiguration configuration
<footer class="position-fixed">
<div><span id="websiteFooter">@Model?.Footer</span><span asp-condition="@Model?.IsDemo">(演示系统)</span></div>
<a id="gotoTop" href="#" class="go-top" title="返回顶部" data-toggle="tooltip" data-placement="left">
<i class="fa fa-angle-up"></i>
</a>
</footer>
@code {
private AdminModel Model;
private AdminModel? Model;
protected override Task OnInitializedAsync()
{
Model = new AdminModel();
Model = new AdminModel(configuration.GetValue("AppId", "BA"));
return Task.CompletedTask;
}
}

View File

@ -5,10 +5,10 @@
<div class="d-flex align-items-center">
<a id="navbar" href="#" class="sidebar-toggle-box">
<i class="fa fa-bars"></i>
<span id="websiteTitle">@Model.Title</span>
<span id="websiteTitle">@Model?.Title</span>
</a>
<div class="nav">
@if (User.IsInRole("Administrators"))
@if (User?.IsInRole("Administrators") ?? false)
{
<!-- tasks start -->
<div class="dropdown">
@ -20,7 +20,7 @@
<div class="dropdown-arrow arrow-primary"></div>
<div id="msgHeaderTaskContent" class="dropdown-header bg-primary">您有 <span id="msgHeaderTask">0</span> 个未完成的任务</div>
<div class="dropdown-footer">
<a href="~/Admin/Tasks">查看所有任务</a>
<a href="/Pages/Admin/Tasks">查看所有任务</a>
</div>
</div>
</div>
@ -35,7 +35,7 @@
<div class="dropdown-arrow arrow-info"></div>
<div id="msgHeaderMsgContent" class="dropdown-header bg-info">您有 <span id="msgHeaderMsg">0</span> 个未读的消息</div>
<div class="dropdown-footer">
<a href="~/Admin/Messages">查看所有消息</a>
<a href="/Pages/Admin/Messages">查看所有消息</a>
</div>
</div>
</div>
@ -65,7 +65,7 @@
<div class="dropdown-arrow arrow-warning"></div>
<div id="msgHeaderAppContent" class="dropdown-header bg-warning">您有 <span id="msgHeaderApp">0</span> 条程序异常通知</div>
<div class="dropdown-footer">
<a href="~/Admin/Exceptions">查看所有异常</a>
<a href="/Pages/Admin/Exceptions">查看所有异常</a>
</div>
</div>
</div>
@ -80,13 +80,13 @@
<div class="dropdown-arrow arrow-danger"></div>
<div id="msgHeaderDbContent" class="dropdown-header bg-danger">您有 <span id="msgHeaderDb">0</span> 条数据库异常通知</div>
<div class="dropdown-footer">
<a href="~/Admin/Exceptions">查看所有异常</a>
<a href="/Pages/Admin/Exceptions">查看所有异常</a>
</div>
</div>
</div>
<!-- db dropdown end -->
<div class="dropdown">
<a class="shadow-success" href="~/Account/Lock" data-toggle="tooltip" title="系统锁屏">
<a class="shadow-success" href="/Account/Lock" data-toggle="tooltip" title="系统锁屏">
<i class="fa fa-tv"></i>
</a>
</div>
@ -94,41 +94,41 @@
</div>
<div class="dropdown userinfo">
<a data-toggle="dropdown" class="dropdown-toggle shadow-default" href="#">
<img id="headerIcon" alt="" src="@Model.Icon" />
<span id="userDisplayName" data-userName="@Model.UserName" class="username text-truncate d-inline-block">@Model.DisplayName</span>
<img id="headerIcon" alt="" src="@Model?.Icon.ToBlazorLink()" />
<span id="userDisplayName" data-userName="@Model?.UserName" class="username text-truncate d-inline-block">@Model?.DisplayName</span>
</a>
<div class="dropdown-menu dropdown-menu-right">
<div class="dropdown-item">
<div class="d-flex flex-fill align-items-center">
<img src="@Model.Icon">
<img src="@Model?.Icon.ToBlazorLink()">
<div class="flex-fill">
<div class="username text-truncate">@Model.DisplayName</div>
<div>登录名:@Model.UserName</div>
<div class="username text-truncate">@Model?.DisplayName</div>
<div>登录名:@Model?.UserName</div>
</div>
</div>
</div>
<div class="dropdown-item">
<a href="~/Admin/Profiles"><i class=" fa fa-suitcase"></i>个人中心</a>
<a href="~/Admin/Index"><i class="fa fa-cog"></i>设置</a>
<a href="~/Admin/Notifications"><i class="fa fa-bell"></i>通知<span id="logoutNoti" class="badge badge-pill badge-success"></span></a>
<a href="/Pages/Admin/Profiles"><i class=" fa fa-suitcase"></i>个人中心</a>
<a href="/Pages/Admin/Index"><i class="fa fa-cog"></i>设置</a>
<a href="/Pages/Admin/Notifications"><i class="fa fa-bell"></i>通知<span id="logoutNoti" class="badge badge-pill badge-success"></span></a>
</div>
<div class="dropdown-item">
<a href="~/Account/Logout"><i class="fa fa-key"></i>注销</a>
<a href="/Account/Logout"><i class="fa fa-key"></i>注销</a>
</div>
</div>
</div>
</div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="~/Admin/Index"><i class="fa fa-home"></i>首页</a></li>
<li class="breadcrumb-item"><a href="/Pages"><i class="fa fa-home"></i>首页</a></li>
<li class="breadcrumb-item d-none" id="breadNav"></li>
</ol>
</nav>
</header>
@code {
private HeaderBarModel Model;
private ClaimsPrincipal User;
private HeaderBarModel? Model;
private ClaimsPrincipal? User;
protected override async Task OnInitializedAsync()
{

View File

@ -1,14 +1,16 @@
@inherits LayoutComponentBase
@inject AuthenticationStateProvider authenticationStateProvider
@inject NavigationManager navigationManager
<Header></Header>
<div class="sidebar">
<NavMenu />
</div>
<section id="main-content" class="main-content @Model.ShowCardTitle">
<input id="lockScreenPeriod" type="hidden" asp-condition="@Model.EnableAutoLockScreen" value="@Model.LockScreenPeriod" />
<section id="main-content" class="main-content @Model?.ShowCardTitle">
<input id="lockScreenPeriod" type="hidden" asp-condition="@Model?.EnableAutoLockScreen" value="@Model?.LockScreenPeriod" />
<div class="main-header">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="~/Admin/Index"><i class="fa fa-home"></i>首页</a></li>
<li class="breadcrumb-item"><a href="/Pages"><i class="fa fa-home"></i>首页</a></li>
<li class="breadcrumb-item d-none"></li>
</ol>
</div>
@ -17,11 +19,18 @@
<Footer></Footer>
@code {
private AdminModel Model;
private AdminModel? Model;
protected override Task OnInitializedAsync()
protected override async Task OnInitializedAsync()
{
var state = await authenticationStateProvider.GetAuthenticationStateAsync();
if (!state.User.Identity.IsAuthenticated)
{
navigationManager.NavigateTo("/Account/Login?returnUrl=" + WebUtility.UrlEncode(new Uri(navigationManager.Uri).PathAndQuery));
}
else
{
Model = new AdminModel();
return Task.CompletedTask;
}
}
}

View File

@ -1,42 +1,43 @@
@inject AuthenticationStateProvider authenticationStateProvider
@inject NavigationManager navigationManager
<aside class="@Model.ShowSideBar">
<aside class="@Model?.ShowSideBar">
<div class="bg"></div>
<div class="nav-brand justify-content-center">
<a href="#">
<img src="@Model.WebSiteLogo" />
<span>@Model.Title</span>
<img src="@Model?.WebSiteLogo.ToBlazorLink()" />
<span>@Model?.Title</span>
</a>
</div>
<div class="nav-header flex-fill align-items-center">
<a href="~/Admin/Profiles">
<img src="@Model.Icon" />
<span class="username d-inline-block text-truncate flex-fill">@Model.DisplayName</span>
<a href="/Pages/Admin/Profiles">
<img src="@Model?.Icon.ToBlazorLink()" />
<span class="username d-inline-block text-truncate flex-fill">@Model?.DisplayName</span>
</a>
</div>
<!-- sidebar menu start-->
<div class="sidebar">
<ul class="nav-sidebar nav nav-pills flex-column flex-nowrap">
@foreach (var menu in Model.Navigations)
@if (Model != null)
{
foreach (var menu in Model.Navigations)
{
<li class="nav-item" id="@string.Format("menus_{0}",menu.Id)">
<a href="@menu.Url" class="nav-link @menu.Active" target="@menu.Target"><i class="@menu.Icon"></i><span class="flex-fill">@menu.Name</span></a>
<a href="@menu.Url.ToBlazorMenuUrl()" class="nav-link @menu.Active" target="@menu.Target"><i class="@menu.Icon"></i><span class="flex-fill">@menu.Name</span></a>
</li>
}
}
</ul>
</div>
<!-- sidebar menu end-->
</aside>
@code {
private NavigatorBarModel Model;
private NavigatorBarModel? Model;
protected override async Task OnInitializedAsync()
{
var state = await authenticationStateProvider.GetAuthenticationStateAsync();
if (state.User.Identity.IsAuthenticated)
{
Model = new NavigatorBarModel(state.User.Identity.Name);
}
}
}

View File

@ -1,33 +0,0 @@
@inject AuthenticationStateProvider authenticationStateProvider
@inject NavigationManager navigationManager
@if (showNotAuthorizedMessage)
{
<section class="section" id="not-authorized-message">
<div class="container">
<div class="notification is-danger">
</div>
</div>
</section>
}
@code {
private bool showNotAuthorizedMessage = false;
protected override async Task OnInitializedAsync()
{
var state = await authenticationStateProvider.GetAuthenticationStateAsync();
if (!state.User.Identity.IsAuthenticated)
{
// If the user is not authenticated redirect them to the sign in page
navigationManager.NavigateTo("/Account/Login?returnUrl=" + System.Net.WebUtility.UrlEncode(new Uri(navigationManager.Uri).PathAndQuery));
}
else
{
// If the user is signed in, but authorization failed, display a message
showNotAuthorizedMessage = true;
}
}
}

View File

@ -3,7 +3,6 @@ using Longbow.Web.SignalR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
@ -44,13 +43,7 @@ namespace Bootstrap.Admin
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(HtmlEncoder.Create(UnicodeRanges.All));
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddLogging(logging => logging.AddFileLogger().AddDBLogger(ExceptionsHelper.Log));
services.AddLogging(logging => logging.AddFileLogger().AddCloudLogger().AddDBLogger(ExceptionsHelper.Log));
services.AddCors();
services.AddResponseCompression();

View File

@ -147,10 +147,11 @@
<input type="text" class="form-control flex-sm-fill" id="url" placeholder="不可为空4000字以内" maxlength="4000" data-valid="true" />
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="category">菜单类别</label>
<select data-toggle="lgbSelect" class="d-none" data-default-val="1" id="category">
<option value="0">系统菜单</option>
<option value="1">外部菜单</option>
<label class="control-label" for="isRes">菜单类型</label>
<select data-toggle="lgbSelect" class="d-none menuChild" data-default-val="0" id="isRes" data-valid="true">
<option value="0">菜单</option>
<option value="1">资源</option>
<option value="2">按钮</option>
</select>
</div>
<div class="form-group col-sm-6">
@ -162,14 +163,6 @@
<option value="_top">顶级窗口</option>
</select>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="isRes">菜单类型</label>
<select data-toggle="lgbSelect" class="d-none" data-default-val="0" id="isRes">
<option value="0">菜单</option>
<option value="1">资源</option>
<option value="2">按钮</option>
</select>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="app">所属应用</label>
<select data-toggle="lgbSelect" class="d-none" data-default-val="@BootstrapAppContext.AppId" id="app">
@ -179,6 +172,13 @@
}
</select>
</div>
<div class="form-group col-sm-6">
<label class="control-label" for="category">菜单类别</label>
<select data-toggle="lgbSelect" class="d-none" disabled data-default-val="1" id="category">
<option value="0">系统菜单</option>
<option value="1">外部菜单</option>
</select>
</div>
</div>
</form>
</div>

View File

@ -1,50 +1,45 @@
@model TaskModel
@{
ViewBag.Title = "任务管理";
Layout = "_Default";
Layout = "_Admin";
}
@section css {
<environment include="Development">
<link href="~/lib/bootstrap-table/bootstrap-table.css" rel="stylesheet" />
</environment>
<environment exclude="Development">
<link href="~/lib/bootstrap-table/bootstrap-table.min.css" rel="stylesheet" />
</environment>
<link href="~/lib/longbow-select/longbow-select.css" rel="stylesheet" />
<link href="~/css/tasks.css" rel="stylesheet" asp-append-version="true" />
}
@section javascript {
<environment include="Development">
<script src="~/lib/bootstrap-table/bootstrap-table.js"></script>
<script src="~/lib/bootstrap-table/extensions/export/bootstrap-table-export.js"></script>
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script>
<script src="~/lib/tablexport/tableExport.js"></script>
<script src="~/lib/validate/jquery.validate.js"></script>
<script src="~/lib/validate/localization/messages_zh.js"></script>
</environment>
<environment exclude="Development">
<script src="~/lib/bootstrap-table/bootstrap-table.min.js"></script>
<script src="~/lib/bootstrap-table/extensions/export/bootstrap-table-export.min.js"></script>
<script src="~/lib/bootstrap-table/locale/bootstrap-table-zh-CN.min.js"></script>
<script src="~/lib/tablexport/tableExport.min.js"></script>
<script src="~/lib/validate/jquery.validate.min.js"></script>
<script src="~/lib/validate/localization/messages_zh.min.js"></script>
</environment>
<script src="~/lib/longbow-checkbox/longbow-checkbox.js"></script>
<script src="~/lib/longbow-select/longbow-select.js"></script>
<script src="~/lib/longbow/longbow.dataentity.js" asp-append-version="true"></script>
<script src="~/lib/longbow/longbow.validate.js" asp-append-version="true"></script>
<script src="~/js/tasks.js" asp-append-version="true"></script>
}
@section tableButtons {
<button class='info btn btn-sm btn-info' asp-auth="info"><i class='fa fa-info'></i><span>日志</span></button>
}
@section cardbody {
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改定时后台任务</span>
</div>
}
@section query {
<div class="alert alert-info">
<p class="font-weight-bold">后台任务说明:</p>
<p>1. 默认任务 (立即执行,仅执行一次)</p>
<p>
<code>
TaskServicesManager.GetOrAdd("简单任务", token => Task.Delay(1000));
</code>
</p>
<p>2. 周期性任务 (1 分钟后间隔 5 秒执行 2 次任务)</p>
<p>
<code>
var trigger = TriggerBuilder.Default.WithInterval(TimeSpan.FromSeconds(5)).WithRepeatCount(2).WithStartTime(DateTimeOffset.Now.AddMinutes(1)).Build();
<br />
TaskServicesManager.GetOrAdd("测试任务", token => Task.Delay(1000), trigger);
</code>
</p>
<p>3. Cron 表达式任务 (间隔 5 秒循环执行任务)</p>
<p>
<code>
TaskServicesManager.GetOrAdd("Cron 表达式任务", token => Task.Delay(1000), TriggerBuilder.Build("*/5 * * * * *"));
</code>
</p>
</div>
}
@section modal {
<div class="modal fade" id="dialogNew" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content" data-toggle="LgbValidate" data-valid-button="#btnSubmit" data-valid-modal="#dialogNew">
<div class="modal-header">
<h5 class="modal-title" id="myModalLabel">任务编辑窗口</h5>
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
@ -73,8 +68,19 @@
</div>
</form>
</div>
}
@section customModal {
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">
<i class="fa fa-times"></i>
<span>关闭</span>
</button>
<button type="button" class="btn btn-primary" id="btnSubmit">
<i class="fa fa-save"></i>
<span>保存</span>
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="dialogLog" tabindex="-1" role="dialog" data-backdrop="static" aria-labelledby="taskModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg" role="document">
<div class="modal-content">
@ -98,3 +104,49 @@
</div>
</div>
}
<div class="card">
<div class="card-header">任务介绍</div>
<div class="card-body">
<div class="alert alert-info">
<p class="font-weight-bold">后台任务说明:</p>
<p>1. 默认任务 (立即执行,仅执行一次)</p>
<p>
<code>
TaskServicesManager.GetOrAdd("简单任务", token => Task.Delay(1000));
</code>
</p>
<p>2. 周期性任务 (1 分钟后间隔 5 秒执行 2 次任务)</p>
<p>
<code>
var trigger = TriggerBuilder.Default.WithInterval(TimeSpan.FromSeconds(5)).WithRepeatCount(2).WithStartTime(DateTimeOffset.Now.AddMinutes(1)).Build();
<br />
TaskServicesManager.GetOrAdd("测试任务", token => Task.Delay(1000), trigger);
</code>
</p>
<p>3. Cron 表达式任务 (间隔 5 秒循环执行任务)</p>
<div>
<code>
TaskServicesManager.GetOrAdd("Cron 表达式任务", token => Task.Delay(1000), TriggerBuilder.Build("*/5 * * * * *"));
</code>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
查询结果
</div>
<div class="card-body">
<div class="alert alert-danger" role="alert" asp-condition="@Model.IsDemo">
<span>演示系统禁止修改定时后台任务</span>
</div>
<table></table>
</div>
</div>
<div id="tableButtons" class="d-none">
<div class='btn-group'>
<button class='pause btn btn-sm btn-danger' asp-auth="pause"><i class='fa fa-pause-circle'></i><span>暂停</span></button>
<button class='run btn btn-sm btn-success' asp-auth="pause"><i class='fa fa-play-circle'></i><span>运行</span></button>
<button class='info btn btn-sm btn-info' asp-auth="info"><i class='fa fa-info-circle'></i><span>日志</span></button>
</div>
</div>

View File

@ -1,4 +1,5 @@
@using System.Net.Http
@using System.Net
@using System.Net.Http
@using System.Security.Claims
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@ -6,7 +7,9 @@
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Mvc
@using Microsoft.Extensions.Configuration
@using Microsoft.JSInterop
@using Bootstrap.Admin
@using Bootstrap.Admin.Shared
@using Bootstrap.Admin.Models
@using Bootstrap.Admin.Pages
@using Bootstrap.Admin.Shared

View File

@ -13,8 +13,15 @@
},
"FileName": "Error\\Log.log",
"MaxFileCount": 1
},
"Cloud": {
"LogLevel": {
"Default": "Error"
},
"Url": "https://client.sdgxgz.com/api/Interface/Log"
}
},
"AutoGenerateDatabase": true,
"DB": [
{
"Enabled": false,
@ -60,6 +67,7 @@
"UseBlazor": true,
"SwaggerPathBase": "",
"AllowOrigins": "http://localhost:49185",
"HealthsCloudUrl": "https://client.sdgxgz.com/api/Interface/Healths",
"FileStorageOptions": {
"Enabled": true,
"Folder": "TaskStorage",

View File

@ -10,11 +10,18 @@
"Default": "Error"
},
"FileName": "Error\\Log.log"
},
"Cloud": {
"LogLevel": {
"Default": "Error"
},
"Url": "https://client.sdgxgz.com/api/Interface/Log"
}
},
"ConnectionStrings": {
"ba": "Data Source=.;Initial Catalog=BootstrapAdmin;User ID=sa;Password=sa"
},
"AutoGenerateDatabase": false,
"DB": [
{
"Enabled": false,
@ -54,8 +61,10 @@
}
],
"AppId": "BA",
"UseBlazor": true,
"SwaggerPathBase": "",
"AllowOrigins": "http://localhost,http://ba.sdgxgz.cn",
"HealthsCloudUrl": "https://client.sdgxgz.com/api/Interface/Healths",
"GiteeHealthChecks": false,
"Sentry": {
"Dsn": "https://70bdfff562e84fa7b9a43d65924ab9ad@sentry.io/1469396"

View File

@ -353,6 +353,10 @@ body.trans-mute * {
right: 45px;
}
li.is-disabled .dd3-content {
color: #c0c4cc;
}
@media (max-width: 480px) {
.dd3-content .menuType {
display: none;

View File

@ -1,4 +1,9 @@
(function ($) {
$.extend({
sendHealths: function (data) {
$.bc({ url: 'api/Interface/Healths', data: JSON.stringify(data), method: 'post' });
}
});
$.fn.extend({
autoScrollSidebar: function (options) {
var option = $.extend({ target: null, offsetTop: 0 }, options);
@ -75,7 +80,7 @@
var resultFormat = {
"Success": '<span class="badge badge-pill badge-success badge-task"><i class="fa fa-check-circle"></i><span>成功</span></span>',
"Timeout": '<span class="badge badge-pill badge-warning badge-task"><i class="fa fa-exclamation-circle"></i><span>超时</span></span>'
}
};
var htmlUserTemplate = '<a class="dropdown-item position-relative" href="{0}"><span class="label label-primary"><i class="fa fa-thumb-tack"></i></span><div class="content">{1}</div><div class="small italic content-task">{2}</div>{3}</a>';
var html = result.Tasks.map(function (u) {
return $.format(htmlUserTemplate, $.formatUrl('Admin/Tasks'), u.Name, u.LastRuntime, resultFormat[u.LastRunResult]);
@ -131,10 +136,10 @@
var html = "";
$.each(menus, function (index, menu) {
if (menu.Menus.length === 0) {
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{4}" data-category="{3}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{5}</span><span class="menuOrder">{4}</span></div></li>', menu.Id, menu.Icon, menu.Name, menu.Category, menu.Order, formatCategoryName(menu));
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{4}" data-category="{3}" data-resource="{6}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{5}</span><span class="menuOrder">{4}</span></div></li>', menu.Id, menu.Icon, menu.Name, menu.Category, menu.Order, formatCategoryName(menu), menu.IsResource);
}
else {
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{5}" data-category="{3}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{6}</span><span class="menuOrder">{5}</span></div><ol class="dd-list">{4}</ol></li>', menu.Id, menu.Icon, menu.Name, menu.Category, cascadeSubMenu(menu.Menus), menu.Order, formatCategoryName(menu));
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{5}" data-category="{3}" data-resource="{7}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{6}</span><span class="menuOrder">{5}</span></div><ol class="dd-list">{4}</ol></li>', menu.Id, menu.Icon, menu.Name, menu.Category, cascadeSubMenu(menu.Menus), menu.Order, formatCategoryName(menu), menu.IsResource);
}
});
return html;
@ -144,10 +149,10 @@
var html = "";
$.each(menus, function (index, menu) {
if (menu.Menus.length === 0) {
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{4}" data-category="{3}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{5}</span><span class="menuOrder">{4}</span></div></li>', menu.Id, menu.Icon, menu.Name, menu.Category, menu.Order, formatCategoryName(menu));
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{4}" data-category="{3}" data-resource="{6}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{5}</span><span class="menuOrder">{4}</span></div></li>', menu.Id, menu.Icon, menu.Name, menu.Category, menu.Order, formatCategoryName(menu), menu.IsResource);
}
else {
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{5}" data-category="{3}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{6}</span><span class="menuOrder">{5}</span></div><ol class="dd-list">{4}</ol></li>', menu.Id, menu.Icon, menu.Name, menu.Category, cascadeSubMenu(menu.Menus), menu.Order, formatCategoryName(menu));
html += $.format('<li class="dd-item dd3-item" data-id="{0}" data-order="{5}" data-category="{3}" data-resource="{7}"><div class="dd-handle dd3-handle"></div><div class="dd3-content"><div class="checkbox"><label><input type="checkbox" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><div class="radio"><label><input type="radio" name="menu" value="{0}"><span><i class="{1}"></i>{2}</span></label></div><span class="menuType">{6}</span><span class="menuOrder">{5}</span></div><ol class="dd-list">{4}</ol></li>', menu.Id, menu.Icon, menu.Name, menu.Category, cascadeSubMenu(menu.Menus), menu.Order, formatCategoryName(menu), menu.IsResource);
}
});
return html;

View File

@ -49,6 +49,8 @@
$.bc({
url: 'healths',
callback: function (result) {
// async send result to cloud
$.sendHealths(result);
var data = $.map(result.Keys, function (name) {
return { Name: name, Duration: result.Report.Entries[name].Duration, Status: result.Report.Entries[name].Status, Exception: result.Report.Entries[name].Exception, Description: result.Report.Entries[name].Description, Data: result.Report.Entries[name].Data };
});

View File

@ -217,6 +217,7 @@ $(function () {
$btnSubmitMenu.data('type', 'parent');
$nestMenuInput.find('label:last').find('input').show();
$nestMenu.find('li.dd-item').hide().remove('[data-id="0"]');
$nestMenu.find('li[data-resource!="0"]').addClass('is-disabled').find(':radio').prop('disabled', true)
$nestMenu.find('li[data-category="' + $category.val() + '"]').show();
showDialog();
});
@ -240,8 +241,16 @@ $(function () {
var type = $(this).data('type');
switch (type) {
case "parent":
$parentMenuID.val($('.dd3-content :radio:checked').val());
// 父级菜单不可以是资源或者按钮类型
var pId = $('.dd3-content :radio:checked').val();
var check = $.remoteValidate('api/Category/ValidateParentMenuById/' + pId);
if (check) {
$parentMenuID.val(pId);
$parentMenuName.val($('.dd3-content :radio:checked').next('span').text());
}
else {
return false;
}
break;
case "order":
var data = $nestMenu.find('li:visible');
@ -333,4 +342,19 @@ $(function () {
});
}
});
// 所属应用更新是联动菜单类别
var $app = $('#app').on('changed.lgbSelect', function (e) {
var defaultVal = $app.attr('data-default-val');
var val = defaultVal === $app.val() ? '0' : '1';
$category.lgbSelect('val', val);
})
if ($.isFunction($.validator)) {
$.validator.addMethod("menuChild", function (value, element) {
var id = $("#menuID").val();
var check = id === "" || value === "菜单" || $.remoteValidate('api/Category/ValidateMenuBySubMenu/' + id);
return check;
}, "拥有子菜单时菜单类型不可更改为资源或者按钮");
}
});

View File

@ -11,7 +11,7 @@
content = $.format(template, 'success', 'play-circle', '运行中');
}
else if (value === "Disabled") {
content = $.format(template, 'danger', 'times-circle', '已禁用');
content = $.format(template, 'danger', 'times-circle', '已停止');
}
return content;
};
@ -33,7 +33,7 @@
return content;
};
$('.card-body table').lgbTable({
var $task = $('.card-body table').lgbTable({
url: Tasks.url,
dataBinder: {
map: {
@ -43,8 +43,8 @@
},
smartTable: {
sidePagination: "client",
sortName: 'CreateTime',
sortOrder: 'desc',
sortName: 'Name',
sortOrder: 'asc',
queryParams: function (params) { return $.extend(params, { operateType: $("#txt_operate_type").val(), OperateTimeStart: $("#txt_operate_start").val(), OperateTimeEnd: $("#txt_operate_end").val() }); },
columns: [
{ title: "名称", field: "Name", sortable: true },
@ -95,6 +95,22 @@
console.log(error);
}
});
},
'click .pause': function (e, value, row, index) {
$.bc({
url: 'api/Tasks/' + row.Name + "?operType=" + "pause", method: 'put',
callback: function (result) {
if (result) $task.bootstrapTable('refresh');
}
});
},
'click .run': function (e, value, row, index) {
$.bc({
url: 'api/Tasks/' + row.Name + "?operType=" + "run", method: 'put',
callback: function (result) {
if (result) $task.bootstrapTable('refresh');
}
});
}
}
}

View File

@ -50,6 +50,10 @@
cursor: not-allowed;
}
.form-select.is-disabled .form-select-append {
color: #c0c4cc;
}
.form-select-input {
color: #606266;
outline: none;

View File

@ -114,11 +114,17 @@
// save attributes
var attrs = [];
["id", "data-default-val"].forEach(function (v, index) {
["id", "name", "class", "data-valid", "data-default-val"].forEach(function (v, index) {
var value = that.$element.attr(v);
if (value !== undefined) attrs.push({ name: v, value: value });
});
var disabled = this.$element.prop('disabled');
// set disabled property
if (disabled) {
this.disabled();
}
// replace element select -> input hidden
this.$element.remove();
this.$element = $('<input type="hidden" data-toggle="lgbSelect" />').val(that.val()).insertBefore(this.$input);
@ -139,12 +145,15 @@
this.$input.removeAttr('disabled');
};
_proto.reset = function (value) {
_proto.reset = function (value, init) {
var that = this;
// keep old value
var oldValue = this.$input.attr('value');
// default select value
if (init == undefined) init = true;
// warning: must use attr('value') method instead of val(). otherwise the others input html element will filled by first element value.
// see https://gitee.com/LongbowEnterprise/longbow-select/issues/IZ3BR?from=project-issue
this.$input.attr('value', '').removeClass('is-valid is-invalid');
@ -153,11 +162,13 @@
$.each(value, function (index) {
var $item = $('<a class="dropdown-item" href="#" data-val="' + this.value + '">' + this.text + '</a>');
that.$menus.append($item);
if (init) {
if (this.selected === true || this.value === oldValue || index === 0 || this.value === that.$element.attr('data-default-val')) {
that.$input.attr('value', this.text);
that.$element.val(this.value).attr('data-text', this.text);
$activeItem = $item;
}
}
});
if ($activeItem !== null) $activeItem.addClass('active');

View File

@ -125,6 +125,19 @@
document.webkitIsFullScreen || window.fullscreen ||
false;
},
remoteValidate: function (url, method) {
if (method === undefined) method = 'get';
var check = false;
jQuery[method]({
url: $.formatUrl(url),
async: false,
cache: false,
success: function (result) {
check = result
}
});
return check;
},
bc: function (options) {
options = $.extend({
id: "",

View File

@ -68,9 +68,13 @@
}
else if (dv !== undefined && ctl.val() === "") target[name] = dv;
else target[name] = ctl.val();
// check boolean value
if (ctl.attr('data-bool') === 'true') {
if (target[name] === "true" || target[name] === "True") target[name] = true;
if (target[name] === "false" || target[name] === "False") target[name] = false;
}
}
return target;
}
};
@ -183,7 +187,7 @@
callback: function (result) {
if (result) {
$(options.bootstrapTable).bootstrapTable('refresh');
handlerCallback.call(that, null, element, { oper: 'save', success: result, data: options.data });
handlerCallback.call(that, null, element, { oper: 'save', success: result, data: [options.data] });
}
}
});

View File

@ -22,8 +22,8 @@ namespace Bootstrap.DataAccess.MongoDB
Id = d.Key,
AppName = d.Value
}).ToList();
var role = RoleHelper.Retrieves().Cast<Role>().FirstOrDefault(r => r.Id == roleId);
apps.ForEach(p => p.Checked = (role != null && (role.Apps.Contains(p.Id)) || role.RoleName.Equals("Administrators", StringComparison.OrdinalIgnoreCase)) ? "checked" : "");
var role = RoleHelper.Retrieves().Cast<Role>().FirstOrDefault(r => r.Id == roleId) ?? new Role();
apps.ForEach(p => p.Checked = (role.Apps.Contains(p.Id) || role.RoleName.Equals("Administrators", StringComparison.OrdinalIgnoreCase)) ? "checked" : "");
return apps;
}

View File

@ -1,4 +1,7 @@
namespace Bootstrap.DataAccess.MongoDB
using Bootstrap.Security;
using MongoDB.Driver;
namespace Bootstrap.DataAccess.MongoDB
{
/// <summary>
/// 自动建库实体操作类
@ -9,9 +12,10 @@
/// 数据库检查方法
/// <paramref name="folder"></paramref>
/// </summary>
public override void CheckDB(string folder)
public override void EnsureCreated(string folder)
{
// UNDONE: 没有环境暂时未写代码
// 检查数据库是否存在
if (DbManager.Dicts.CountDocuments(FilterDefinition<BootstrapDict>.Empty) == 0) GenerateDB(folder);
}
}
}

View File

@ -5,8 +5,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bootstrap.Security" Version="3.0.0" />
<PackageReference Include="MongoDB.Driver" Version="2.9.2" />
<PackageReference Include="Bootstrap.Security" Version="3.0.1" />
<PackageReference Include="MongoDB.Driver" Version="2.9.3" />
</ItemGroup>
<ItemGroup>

View File

@ -13,7 +13,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// </summary>
internal static class DbManager
{
private static IMongoDatabase _db = null;
private static IMongoDatabase? _db = null;
private static bool _register = false;
private static readonly object _locker = new object();
@ -38,7 +38,9 @@ namespace Bootstrap.DataAccess.MongoDB
InitDb();
}
}
#pragma warning disable CS8603 // 可能的 null 引用返回。
return _db;
#pragma warning restore CS8603 // 可能的 null 引用返回。
}
}

View File

@ -41,7 +41,6 @@ namespace Bootstrap.DataAccess.MongoDB
{
if (string.IsNullOrEmpty(p.Id))
{
p.Id = null;
DbManager.Dicts.InsertOne(p);
p.Id = DbManager.Dicts.Find(d => d.Name == p.Name && d.Category == p.Category && d.Define == p.Define && d.Code == p.Code).FirstOrDefault().Id;
}

View File

@ -1,4 +1,4 @@
using Longbow.Web.Mvc;
using Longbow.Web.Mvc;
using MongoDB.Driver;
using PetaPoco;
using System;
@ -75,12 +75,9 @@ namespace Bootstrap.DataAccess.MongoDB
// sort
var sortBuilder = Builders<DataAccess.Exceptions>.Sort;
SortDefinition<DataAccess.Exceptions> sort = null;
var sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.LogTime) : sortBuilder.Descending(t => t.LogTime);
switch (po.Sort)
{
case "LogTime":
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.LogTime) : sortBuilder.Descending(t => t.LogTime);
break;
case "ErrorPage":
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.ErrorPage) : sortBuilder.Descending(t => t.ErrorPage);
break;

View File

@ -13,7 +13,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// <summary>
///
/// </summary>
public IEnumerable<string> Roles { get; set; }
public IEnumerable<string> Roles { get; set; } = new List<string>();
/// <summary>
///

View File

@ -20,7 +20,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// <param name="endTime"></param>
/// <param name="opType"></param>
/// <returns></returns>
public override Page<DataAccess.Log> RetrievePages(PaginationOption po, DateTime? startTime, DateTime? endTime, string opType)
public override Page<DataAccess.Log> RetrievePages(PaginationOption po, DateTime? startTime, DateTime? endTime, string? opType)
{
var filterBuilder = Builders<DataAccess.Log>.Filter;
var filter = filterBuilder.Empty;
@ -31,7 +31,7 @@ namespace Bootstrap.DataAccess.MongoDB
// sort
var sortBuilder = Builders<DataAccess.Log>.Sort;
SortDefinition<DataAccess.Log> sort = null;
var sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.LogTime) : sortBuilder.Descending(t => t.LogTime);
switch (po.Sort)
{
case "CRUD":
@ -40,9 +40,6 @@ namespace Bootstrap.DataAccess.MongoDB
case "UserName":
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.UserName) : sortBuilder.Descending(t => t.UserName);
break;
case "LogTime":
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.LogTime) : sortBuilder.Descending(t => t.LogTime);
break;
case "Ip":
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.Ip) : sortBuilder.Descending(t => t.Ip);
break;
@ -70,7 +67,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// <param name="endTime"></param>
/// <param name="opType"></param>
/// <returns></returns>
public override IEnumerable<DataAccess.Log> RetrieveAll(DateTime? startTime, DateTime? endTime, string opType)
public override IEnumerable<DataAccess.Log> RetrieveAll(DateTime? startTime, DateTime? endTime, string? opType)
{
var filterBuilder = Builders<DataAccess.Log>.Filter;
var filter = filterBuilder.Empty;

View File

@ -31,7 +31,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// <param name="endTime"></param>
/// <param name="ip"></param>
/// <returns></returns>
public override Page<DataAccess.LoginUser> RetrieveByPages(PaginationOption po, DateTime? startTime, DateTime? endTime, string ip)
public override Page<DataAccess.LoginUser> RetrieveByPages(PaginationOption po, DateTime? startTime, DateTime? endTime, string? ip)
{
var logs = RetrieveAll(startTime, endTime, ip);
return new Page<DataAccess.LoginUser>()
@ -49,7 +49,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// 获取所有登录数据
/// </summary>
/// <returns></returns>
public override IEnumerable<DataAccess.LoginUser> RetrieveAll(DateTime? startTime, DateTime? endTime, string ip)
public override IEnumerable<DataAccess.LoginUser> RetrieveAll(DateTime? startTime, DateTime? endTime, string? ip)
{
var filterBuilder = Builders<DataAccess.LoginUser>.Filter;
var filter = filterBuilder.Empty;

View File

@ -40,8 +40,8 @@ namespace Bootstrap.DataAccess.MongoDB
var dicts = DictHelper.RetrieveDicts().Where(m => m.Category == "菜单");
menus.ForEach(m =>
{
m.CategoryName = dicts.FirstOrDefault(d => d.Code == m.Category)?.Name;
if (m.ParentId != "0") m.ParentName = menus.FirstOrDefault(p => p.Id == m.ParentId)?.Name;
m.CategoryName = dicts.FirstOrDefault(d => d.Code == m.Category)?.Name ?? "";
if (m.ParentId != "0") m.ParentName = menus.FirstOrDefault(p => p.Id == m.ParentId)?.Name ?? "";
});
return menus;
@ -56,7 +56,6 @@ namespace Bootstrap.DataAccess.MongoDB
{
if (string.IsNullOrEmpty(p.Id))
{
p.Id = null;
DbManager.Menus.InsertOne(p);
p.Id = DbManager.Menus.Find(m => m.Name == p.Name && m.Category == p.Category && m.ParentId == p.ParentId && m.Url == p.Url).FirstOrDefault().Id;
}

View File

@ -8,19 +8,18 @@ namespace Bootstrap.DataAccess.MongoDB
public class RejectUser
{
/// <summary>
/// 获得/设置 操作日志主键ID
///
/// </summary>
public string Id { get; set; }
public string? Id { get; set; }
/// <summary>
///
/// </summary>
public string UserName { get; set; } = "";
/// <summary>
///
/// </summary>
public string UserName { get; set; }
/// <summary>
///
/// </summary>
public string DisplayName { get; set; }
public string DisplayName { get; set; } = "";
/// <summary>
///
@ -30,7 +29,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// <summary>
///
/// </summary>
public string RejectedBy { get; set; }
public string RejectedBy { get; set; } = "";
/// <summary>
///
@ -40,6 +39,6 @@ namespace Bootstrap.DataAccess.MongoDB
/// <summary>
///
/// </summary>
public string RejectedReason { get; set; }
public string RejectedReason { get; set; } = "";
}
}

View File

@ -13,12 +13,12 @@ namespace Bootstrap.DataAccess.MongoDB
/// <summary>
/// 此角色关联的所有菜单
/// </summary>
public IEnumerable<string> Menus { get; set; }
public IEnumerable<string> Menus { get; set; } = new List<string>();
/// <summary>
/// 此角色关联的所有应用程序
/// </summary>
public IEnumerable<string> Apps { get; set; }
public IEnumerable<string> Apps { get; set; } = new List<string>();
/// <summary>
///
@ -41,9 +41,7 @@ namespace Bootstrap.DataAccess.MongoDB
DbManager.Roles.InsertOne(new Role()
{
RoleName = p.RoleName,
Description = p.Description,
Menus = new List<string>(),
Apps = new List<string>()
Description = p.Description
});
p.Id = DbManager.Roles.Find(r => r.RoleName == p.RoleName && r.Description == p.Description).FirstOrDefault().Id;
}
@ -130,7 +128,7 @@ namespace Bootstrap.DataAccess.MongoDB
{
var roles = RoleHelper.Retrieves().Cast<Role>().ToList();
roles.ForEach(r => r.Checked = (r.Menus != null && r.Menus.Contains(menuId)) ? "checked" : "");
roles.ForEach(r => r.Menus = null);
roles.ForEach(r => r.Menus = new List<string>());
return roles;
}
@ -142,26 +140,41 @@ namespace Bootstrap.DataAccess.MongoDB
/// <returns></returns>
public override bool SavaByMenuId(string menuId, IEnumerable<string> roleIds)
{
// 参数 id 可能是子菜单
// https://gitee.com/LongbowEnterprise/dashboard/issues?id=IQW93
if (!string.IsNullOrEmpty(menuId))
{
// 找到所有包含此菜单的角色集合
var roles = DbManager.Roles.Find(md => md.Menus != null && md.Menus.Contains(menuId)).ToList();
// Remove roles
// 所有角色集合移除此菜单
roles.ForEach(p =>
{
var menus = p.Menus == null ? new List<string>() : p.Menus.ToList();
var menus = p.Menus.ToList();
menus.Remove(menuId);
DbManager.Roles.UpdateOne(md => md.Id == p.Id, Builders<Role>.Update.Set(md => md.Menus, menus));
});
// 授权角色集合
string? parentId = menuId;
roles = DbManager.Roles.Find(md => roleIds.Contains(md.Id)).ToList();
do
{
roles.ForEach(role =>
{
var menus = role.Menus == null ? new List<string>() : role.Menus.ToList();
if (!menus.Contains(menuId))
var menus = role.Menus.ToList();
if (!menus.Contains(parentId))
{
menus.Add(menuId);
menus.Add(parentId);
DbManager.Roles.UpdateOne(md => md.Id == role.Id, Builders<Role>.Update.Set(md => md.Menus, menus));
}
});
// 查找父级菜单
parentId = DbManager.Menus.Find(md => md.Id == parentId).FirstOrDefault()?.ParentId;
}
while (!string.IsNullOrEmpty(parentId) && parentId != "0");
}
return true;
}

View File

@ -16,7 +16,7 @@ namespace Bootstrap.DataAccess.MongoDB
///
/// </summary>
/// <returns></returns>
public override Page<DataAccess.Trace> RetrievePages(PaginationOption po, DateTime? startTime, DateTime? endTime, string ip)
public override Page<DataAccess.Trace> RetrievePages(PaginationOption po, DateTime? startTime, DateTime? endTime, string? ip)
{
// filter
var filterBuilder = Builders<DataAccess.Trace>.Filter;
@ -28,12 +28,9 @@ namespace Bootstrap.DataAccess.MongoDB
// sort
var sortBuilder = Builders<DataAccess.Trace>.Sort;
SortDefinition<DataAccess.Trace> sort = null;
var sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.LogTime) : sortBuilder.Descending(t => t.LogTime);
switch (po.Sort)
{
case "LogTime":
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.LogTime) : sortBuilder.Descending(t => t.LogTime);
break;
case "IP":
sort = po.Order == "asc" ? sortBuilder.Ascending(t => t.Ip) : sortBuilder.Descending(t => t.Ip);
break;
@ -73,7 +70,7 @@ namespace Bootstrap.DataAccess.MongoDB
/// <param name="endTime"></param>
/// <param name="ip"></param>
/// <returns></returns>
public override IEnumerable<DataAccess.Trace> RetrieveAll(DateTime? startTime, DateTime? endTime, string ip)
public override IEnumerable<DataAccess.Trace> RetrieveAll(DateTime? startTime, DateTime? endTime, string? ip)
{
var filterBuilder = Builders<DataAccess.Trace>.Filter;
var filter = filterBuilder.Empty;

View File

@ -1,4 +1,5 @@
using Bootstrap.Security;
using Bootstrap.Security.Mvc;
using Longbow.Security.Cryptography;
using MongoDB.Driver;
using System;
@ -15,19 +16,19 @@ namespace Bootstrap.DataAccess.MongoDB
/// <summary>
///
/// </summary>
public IEnumerable<string> Roles { get; set; }
public IEnumerable<string> Roles { get; set; } = new List<string>();
/// <summary>
///
/// </summary>
public IEnumerable<string> Groups { get; set; }
public IEnumerable<string> Groups { get; set; } = new List<string>();
/// <summary>
///
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public override BootstrapUser RetrieveUserByUserName(string userName)
public override BootstrapUser? RetrieveUserByUserName(string userName)
{
var project = Builders<User>.Projection.Include(u => u.Id)
.Include(u => u.UserName)
@ -39,7 +40,7 @@ namespace Bootstrap.DataAccess.MongoDB
if (ret != null)
{
if (string.IsNullOrEmpty(ret.Icon)) ret.Icon = "default.jpg";
if (string.IsNullOrEmpty(ret.App)) ret.App = "0";
if (string.IsNullOrEmpty(ret.App)) ret.App = BootstrapAppContext.AppId;
}
return ret;
}
@ -118,8 +119,6 @@ namespace Bootstrap.DataAccess.MongoDB
RegisterTime = DateTime.Now,
ApprovedTime = user.ApprovedTime,
ApprovedBy = user.ApprovedBy,
Roles = new List<string>(),
Groups = new List<string>(),
Icon = user.Icon,
Description = user.Description,
IsReset = 0

View File

@ -14,17 +14,17 @@ namespace Bootstrap.DataAccess
/// <summary>
/// 获得/设置 应用程序主键ID
/// </summary>
public string Id { get; set; }
public string? Id { get; set; }
/// <summary>
/// 获得/设置 群组名称
/// </summary>
public string AppName { get; set; }
public string AppName { get; set; } = "未设置";
/// <summary>
/// 获取/设置 用户群组关联状态 checked 标示已经关联 '' 标示未关联
/// </summary>
public string Checked { get; set; }
public string Checked { get; set; } = "";
/// <summary>
/// 根据角色ID指派部门
@ -33,7 +33,8 @@ namespace Bootstrap.DataAccess
/// <returns></returns>
public virtual IEnumerable<App> RetrievesByRoleId(string roleId)
{
var ret = DbManager.Create().Fetch<App>($"select d.Code as Id, d.Name as AppName, case ra.AppId when d.Code then 'checked' else '' end Checked from Dicts d left join RoleApp ra on d.Code = ra.AppId and ra.RoleId = @1 where d.Code > '0' and d.Category = @0", "应用程序", roleId);
using var db = DbManager.Create();
var ret = db.Fetch<App>($"select d.Code as Id, d.Name as AppName, case ra.AppId when d.Code then 'checked' else '' end Checked from Dicts d left join RoleApp ra on d.Code = ra.AppId and ra.RoleId = @1 where d.Category = @0", "应用程序", roleId);
// 判断是否为Administrators
var role = RoleHelper.Retrieves().FirstOrDefault(r => r.Id == roleId);

View File

@ -11,36 +11,30 @@ namespace Bootstrap.DataAccess
/// </summary>
public class AutoDB
{
private string _folder;
/// <summary>
/// 数据库检查方法
/// </summary>
public virtual void CheckDB(string folder)
{
_folder = folder;
using (var db = Longbow.Data.DbManager.Create())
public virtual void EnsureCreated(string folder)
{
using var db = Longbow.Data.DbManager.Create();
db.CommandTimeout = 5000;
switch (db.Provider.GetType().Name)
{
case "SQLiteDatabaseProvider":
if (db.ExecuteScalar<int>("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Users'") == 0) GenerateSQLiteDB(db);
if (db.ExecuteScalar<int>("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='Users'") == 0) GenerateSQLiteDB(db, folder);
break;
case "SqlServerDatabaseProvider":
using (var newDB = ModifyConnectionString(db))
{
if (newDB.ExecuteScalar<int?>("select COUNT(1) from sys.databases where name = N'BootstrapAdmin'") == 0) GenerateSqlServer();
if (newDB.ExecuteScalar<int?>("select COUNT(*) from sys.databases where name = N'BootstrapAdmin'") == 0) GenerateDB(folder);
}
break;
case "MySqlDatabaseProvider":
case "MariaDbDatabaseProvider":
// UNDONE: 本地没有环境此处代码未测试
if (db.ExecuteScalar<int>("select count(*) from information_schema.tables where table_name ='Users' and Table_Schema = 'BootstrapAdmin'") == 0) GenerateMySql();
if (db.ExecuteScalar<int>("select count(*) from information_schema.tables where table_name ='Users' and Table_Schema = 'BootstrapAdmin'") == 0) GenerateDB(folder);
break;
}
}
}
private IDatabase ModifyConnectionString(IDatabase db)
{
@ -56,9 +50,8 @@ namespace Bootstrap.DataAccess
return new Database(string.Join(";", newsegs), provider);
}
private void GenerateSQLiteDB(IDatabase db)
private void GenerateSQLiteDB(IDatabase db, string folder)
{
var folder = _folder;
var initFile = Path.Combine(folder, "Install.sql");
if (File.Exists(initFile))
{
@ -74,21 +67,19 @@ namespace Bootstrap.DataAccess
}
}
private void GenerateSqlServer()
/// <summary>
/// 执行建库脚本
/// </summary>
protected void GenerateDB(string folder)
{
// 检查 install.ps1 脚本
var file = Path.Combine(_folder, $"install.ps1");
var file = Path.Combine(folder, $"install.ps1");
if (File.Exists(file))
{
var psi = new ProcessStartInfo("powershell", $"{file} \"{_folder}\"");
var psi = new ProcessStartInfo("powershell", $"{file} \"{folder}\"");
var p = Process.Start(psi);
p.WaitForExit();
}
}
private void GenerateMySql()
{
// UNDONE: 没有环境暂时未写代码
}
}
}

View File

@ -5,22 +5,22 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bootstrap.Security.DataAccess" Version="3.0.1-beta3" />
<PackageReference Include="Bootstrap.Security.Mvc" Version="3.0.2-beta7" />
<PackageReference Include="Longbow" Version="3.0.0" />
<PackageReference Include="Longbow.Cache" Version="3.0.1-beta2" />
<PackageReference Include="Longbow.Data" Version="3.0.1-beta1" />
<PackageReference Include="Longbow.GiteeAuth" Version="3.0.1-beta1" />
<PackageReference Include="Longbow.GitHubAuth" Version="3.0.1-beta1" />
<PackageReference Include="Longbow.OAuth" Version="3.0.1" />
<PackageReference Include="Bootstrap.Security.DataAccess" Version="3.0.1" />
<PackageReference Include="Bootstrap.Security.Mvc" Version="3.0.2" />
<PackageReference Include="Longbow" Version="3.0.1" />
<PackageReference Include="Longbow.Cache" Version="3.0.3-beta1" />
<PackageReference Include="Longbow.Data" Version="3.0.1" />
<PackageReference Include="Longbow.GiteeAuth" Version="3.0.1" />
<PackageReference Include="Longbow.GitHubAuth" Version="3.0.1" />
<PackageReference Include="Longbow.OAuth" Version="3.0.2" />
<PackageReference Include="Longbow.PetaPoco" Version="1.0.2" />
<PackageReference Include="Longbow.Security.Cryptography" Version="1.3.0" />
<PackageReference Include="Longbow.Tasks" Version="3.0.0" />
<PackageReference Include="Longbow.Web" Version="3.0.1-beta1" />
<PackageReference Include="Longbow.WeChatAuth" Version="3.0.0" />
<PackageReference Include="Longbow.Tasks" Version="3.0.2-beta2" />
<PackageReference Include="Longbow.Web" Version="3.0.1" />
<PackageReference Include="Longbow.WeChatAuth" Version="3.0.1" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="3.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.7.0" />
<PackageReference Include="PetaPoco.Extensions" Version="3.0.0" />
<PackageReference Include="PetaPoco.Extensions" Version="3.0.1" />
</ItemGroup>
</Project>

View File

@ -20,7 +20,7 @@ namespace Bootstrap.DataAccess
/// <param name="appIds"></param>
/// <param name="dictIds"></param>
/// <param name="cacheKey"></param>
public static void ClearCache(IEnumerable<string> roleIds = null, IEnumerable<string> userIds = null, IEnumerable<string> groupIds = null, IEnumerable<string> menuIds = null, IEnumerable<string> appIds = null, IEnumerable<string> dictIds = null, string cacheKey = null)
public static void ClearCache(IEnumerable<string>? roleIds = null, IEnumerable<string>? userIds = null, IEnumerable<string>? groupIds = null, IEnumerable<string>? menuIds = null, IEnumerable<string>? appIds = null, IEnumerable<string>? dictIds = null, string? cacheKey = null)
{
var cacheKeys = new List<string>();
var corsKeys = new List<string>();

View File

@ -10,20 +10,21 @@ namespace Bootstrap.DataAccess
[TableName("DBLogs")]
public class DBLog
{
/// <summary>
/// 获得/设置 主键ID
/// </summary>
public string Id { get; set; }
public string? Id { get; set; }
/// <summary>
/// 获得/设置 当前登陆名
/// </summary>
public string UserName { get; set; }
public string? UserName { get; set; }
/// <summary>
/// 获得/设置 数据库执行脚本
/// </summary>
public string SQL { get; set; }
public string SQL { get; set; } = "";
/// <summary>
/// 获取/设置 用户角色关联状态 checked 标示已经关联 '' 标示未关联
@ -38,8 +39,10 @@ namespace Bootstrap.DataAccess
/// <param name="endTime"></param>
/// <param name="userName"></param>
/// <returns></returns>
public virtual Page<DBLog> RetrievePages(PaginationOption po, DateTime? startTime, DateTime? endTime, string userName)
public virtual Page<DBLog> RetrievePages(PaginationOption po, DateTime? startTime, DateTime? endTime, string? userName)
{
if (string.IsNullOrEmpty(po.Sort)) po.Sort = "LogTime";
if (string.IsNullOrEmpty(po.Order)) po.Order = "desc";
var sql = new Sql("select * from DBLogs");
if (startTime.HasValue) sql.Where("LogTime >= @0", startTime.Value);
if (endTime.HasValue) sql.Where("LogTime < @0", endTime.Value.AddDays(1).AddSeconds(-1));
@ -47,7 +50,8 @@ namespace Bootstrap.DataAccess
if (!string.IsNullOrEmpty(userName)) sql.Where("UserName = @0", userName);
sql.OrderBy($"{po.Sort} {po.Order}");
return DbManager.Create().Page<DBLog>(po.PageIndex, po.Limit, sql);
using var db = DbManager.Create();
return db.Page<DBLog>(po.PageIndex, po.Limit, sql);
}
/// <summary>
@ -72,7 +76,8 @@ namespace Bootstrap.DataAccess
{
if (p == null) throw new ArgumentNullException(nameof(p));
DeleteLogAsync();
DbManager.Create(enableLog: false).Save(p);
using var db = DbManager.Create(enableLog: false);
db.Save(p);
return true;
}
}

View File

@ -10,18 +10,19 @@ namespace Bootstrap.DataAccess
/// <summary>
/// 创建数据库实体类时发生异常实例
/// </summary>
public static Exception Exception { get; set; }
public static Exception? Exception { get; private set; }
/// <summary>
/// 根据配置文件动态创建数据库实体类方法
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static T Create<T>()
public static T? Create<T>() where T : class
{
T t = default;
T? t = default;
try
{
Exception = null;
t = Longbow.Data.DbContextManager.Create<T>();
}
catch (Exception ex)

View File

@ -20,7 +20,7 @@ namespace Bootstrap.DataAccess
/// <param name="keepAlive"></param>
/// <param name="enableLog">是否记录日志</param>
/// <returns></returns>
public static IDatabase Create(string connectionName = null, bool keepAlive = false, bool enableLog = true)
public static IDatabase Create(string? connectionName = null, bool keepAlive = false, bool enableLog = true)
{
if (Mappers.GetMapper(typeof(Exceptions), null) == null) Mappers.Register(typeof(Exceptions).Assembly, new BootstrapDataAccessConventionMapper());
var db = Longbow.Data.DbManager.Create(connectionName, keepAlive);

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