😎调整规范化插件工程

This commit is contained in:
zuohuaijun 2025-09-14 19:14:32 +08:00
parent df56329943
commit 0a01141fef
203 changed files with 1293 additions and 14708 deletions

View File

@ -48,11 +48,7 @@
<ItemGroup>
<ProjectReference Include="..\Admin.NET.Core\Admin.NET.Core.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.Ai\Admin.NET.Plugin.Ai.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.ApprovalFlow\Admin.NET.Plugin.ApprovalFlow.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.DingTalk\Admin.NET.Plugin.DingTalk.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.GoView\Admin.NET.Plugin.GoView.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.K3Cloud\Admin.NET.Plugin.K3Cloud.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.PaddleOCR\Admin.NET.Plugin.PaddleOCR.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.ReZero\Admin.NET.Plugin.ReZero.csproj" />
<ProjectReference Include="..\Plugins\Admin.NET.Plugin.WorkWeixin\Admin.NET.Plugin.WorkWeixin.csproj" />
</ItemGroup>

View File

@ -92,7 +92,7 @@ public class SysRoleTableService : ITransient
var ignoreTables = new List<string>
{
//"DingTalkUser",
//"ApprovalFlow",
//"DataApproval",
//"GoViewPro",
//"GoViewPro",
//"GoViewProData"

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36429.23 d17.14
VisualStudioVersion = 17.14.36429.23
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Application", "Admin.NET.Application\Admin.NET.Application.csproj", "{C3F5AEC5-ACEE-4109-94E3-3F981DC18268}"
EndProject
@ -24,8 +24,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Plugin.DingTalk",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Plugin.ReZero", "Plugins\Admin.NET.Plugin.ReZero\Admin.NET.Plugin.ReZero.csproj", "{04AB2E76-DE8B-4EFD-9F48-F8D4C0993106}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Plugin.ApprovalFlow", "Plugins\Admin.NET.Plugin.ApprovalFlow\Admin.NET.Plugin.ApprovalFlow.csproj", "{4124E31B-EA94-4EE3-9EC6-A565F1420AEA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Plugin.K3Cloud", "Plugins\Admin.NET.Plugin.K3Cloud\Admin.NET.Plugin.K3Cloud.csproj", "{9EB9C39E-E14F-443E-9AA3-EE417ABCBC1D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.NET.Plugin.PaddleOCR", "Plugins\Admin.NET.Plugin.PaddleOCR\Admin.NET.Plugin.PaddleOCR.csproj", "{1B106C11-E5BF-44AB-A283-1E948A8BD8C2}"
@ -36,6 +34,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin.NET.Test", "Admin.NET
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin.NET.Plugin.Ai", "Plugins\Admin.NET.Plugin.Ai\Admin.NET.Plugin.Ai.csproj", "{EB254721-C73C-4B1F-B9D7-0D989409F0C8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin.NET.Plugin.DataApproval", "Plugins\Admin.NET.Plugin.DataApproval\Admin.NET.Plugin.DataApproval.csproj", "{BCB22734-58C1-5B78-60E8-EE68E9F24333}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -70,10 +70,6 @@ Global
{04AB2E76-DE8B-4EFD-9F48-F8D4C0993106}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04AB2E76-DE8B-4EFD-9F48-F8D4C0993106}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04AB2E76-DE8B-4EFD-9F48-F8D4C0993106}.Release|Any CPU.Build.0 = Release|Any CPU
{4124E31B-EA94-4EE3-9EC6-A565F1420AEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4124E31B-EA94-4EE3-9EC6-A565F1420AEA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4124E31B-EA94-4EE3-9EC6-A565F1420AEA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4124E31B-EA94-4EE3-9EC6-A565F1420AEA}.Release|Any CPU.Build.0 = Release|Any CPU
{9EB9C39E-E14F-443E-9AA3-EE417ABCBC1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9EB9C39E-E14F-443E-9AA3-EE417ABCBC1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9EB9C39E-E14F-443E-9AA3-EE417ABCBC1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -94,6 +90,10 @@ Global
{EB254721-C73C-4B1F-B9D7-0D989409F0C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB254721-C73C-4B1F-B9D7-0D989409F0C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB254721-C73C-4B1F-B9D7-0D989409F0C8}.Release|Any CPU.Build.0 = Release|Any CPU
{BCB22734-58C1-5B78-60E8-EE68E9F24333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BCB22734-58C1-5B78-60E8-EE68E9F24333}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BCB22734-58C1-5B78-60E8-EE68E9F24333}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BCB22734-58C1-5B78-60E8-EE68E9F24333}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -102,11 +102,11 @@ Global
{C4A288D5-0FAA-4F43-9072-B97635D7871D} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{F6A002AD-CF7F-4771-8597-F12A50A93DAA} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{04AB2E76-DE8B-4EFD-9F48-F8D4C0993106} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{4124E31B-EA94-4EE3-9EC6-A565F1420AEA} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{9EB9C39E-E14F-443E-9AA3-EE417ABCBC1D} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{1B106C11-E5BF-44AB-A283-1E948A8BD8C2} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{12998618-A875-4580-B5B1-0CC50CE85F27} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{EB254721-C73C-4B1F-B9D7-0D989409F0C8} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
{BCB22734-58C1-5B78-60E8-EE68E9F24333} = {76F70D22-8D53-468E-A3B6-1704666A1D71}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5CD801D7-984A-4F5C-8FA2-211B7A5EA9F3}

View File

@ -1,4 +1,11 @@
{
"[openapi:AiChat]": {
"Group": "AiChat",
"Title": "AiChat",
"Description": "LLM 大模型",
"Version": "1.0.0",
"Order": 100
},
"LLM": {
"ModelProvider": "DeepSeek", // 使ProvidersProductName
"InitSystemChatMessage": "你是一个经验丰富的 Admin.NET 人工智能助手,请根据用户的问题给出准确的回答。- **回答请以markdown格式输出**;- **适当加入emoji表达人类情感使内容更易于理解与传播。**",

View File

@ -1,15 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow;
/// <summary>
/// 流程类型枚举
/// </summary>
[Description("流程类型枚举")]
public enum FlowTypeEnum
{
}

View File

@ -1,20 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.IPlugins;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Admin.NET.Plugin.Core.Abstractions.Pay;
public interface IPayPlugin : IPlugin
{
string Name { get; set; }
string Say();
}

View File

@ -1,27 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<NoWarn>1701;1702;8616;1591;8618;8619;8629;8602;8603;8604;8625;8765</NoWarn>
<DocumentationFile>Admin.NET.Plugin.Core.xml</DocumentationFile>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Const\**" />
<Compile Remove="Extensions\**" />
<Compile Remove="Options\**" />
<EmbeddedResource Remove="Const\**" />
<EmbeddedResource Remove="Extensions\**" />
<EmbeddedResource Remove="Options\**" />
<None Remove="Const\**" />
<None Remove="Extensions\**" />
<None Remove="Options\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PluginCore\PluginCore.AspNetCore\PluginCore.AspNetCore.csproj" />
</ItemGroup>
</Project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>Admin.NET.Plugin.Core</name>
</assembly>
<members>
</members>
</doc>

View File

@ -1,13 +1,13 @@
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"[openapi:审批流程]": {
"Group": "审批流程",
"Title": "审批流程",
"[openapi:DataApproval]": {
"Group": "DataApproval",
"Title": "DataApproval",
"Description": "对业务实体数据的增删改操作进行流程审批。",
"Version": "1.0.0",
"Order": 100
},
"ApprovalFlow": {
"DataApproval": {
}
}

View File

@ -4,13 +4,13 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow;
namespace Admin.NET.Plugin.DataApproval;
/// <summary>
/// 审批流程相关常量
/// 数据审批相关常量
/// </summary>
[Const("审批流程相关常量")]
public class ApprovalFlowConst
[Const("数据审批相关常量")]
public class DataApprovalConst
{
/// <summary>
/// API分组名称

View File

@ -1,62 +1,62 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow;
/// <summary>
/// 审批流表单
/// </summary>
[SugarTable(null, "审批流表单")]
public class ApprovalForm : EntityBaseData
{
/// <summary>
/// 编号
/// </summary>
[SugarColumn(ColumnDescription = "编号", Length = 32)]
public string? Code { get; set; }
/// <summary>
/// 名称
/// </summary>
[SugarColumn(ColumnDescription = "名称", Length = 32)]
public string? Name { get; set; }
/// <summary>
/// 表单名称
/// </summary>
[SugarColumn(ColumnDescription = "表单名称", Length = 32)]
public string? FormName { get; set; }
/// <summary>
/// 表单属性
/// </summary>
[SugarColumn(ColumnDescription = "表单属性", Length = 32)]
public string? FormType { get; set; }
/// <summary>
/// 表单状态
/// </summary>
[SugarColumn(ColumnDescription = "表单状态")]
public int? FormStatus { get; set; }
/// <summary>
/// 表单结果
/// </summary>
[SugarColumn(ColumnDescription = "表单结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormResult { get; set; }
/// <summary>
/// 状态
/// </summary>
[SugarColumn(ColumnDescription = "状态")]
public int? Status { get; set; }
/// <summary>
/// 备注
/// </summary>
[SugarColumn(ColumnDescription = "备注", Length = 255)]
public string? Remark { get; set; }
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.DataApproval;
/// <summary>
/// 审批流表单
/// </summary>
[SugarTable(null, "审批流表单")]
public class ApprovalForm : EntityBaseData
{
/// <summary>
/// 编号
/// </summary>
[SugarColumn(ColumnDescription = "编号", Length = 32)]
public string? Code { get; set; }
/// <summary>
/// 名称
/// </summary>
[SugarColumn(ColumnDescription = "名称", Length = 32)]
public string? Name { get; set; }
/// <summary>
/// 表单名称
/// </summary>
[SugarColumn(ColumnDescription = "表单名称", Length = 32)]
public string? FormName { get; set; }
/// <summary>
/// 表单属性
/// </summary>
[SugarColumn(ColumnDescription = "表单属性", Length = 32)]
public string? FormType { get; set; }
/// <summary>
/// 表单状态
/// </summary>
[SugarColumn(ColumnDescription = "表单状态")]
public int? FormStatus { get; set; }
/// <summary>
/// 表单结果
/// </summary>
[SugarColumn(ColumnDescription = "表单结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormResult { get; set; }
/// <summary>
/// 状态
/// </summary>
[SugarColumn(ColumnDescription = "状态")]
public int? Status { get; set; }
/// <summary>
/// 备注
/// </summary>
[SugarColumn(ColumnDescription = "备注", Length = 255)]
public string? Remark { get; set; }
}

View File

@ -1,56 +1,56 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow;
/// <summary>
/// 审批流表单记录
/// </summary>
[SugarTable(null, "审批流表单记录")]
public class ApprovalFormRecord : EntityBaseData
{
/// <summary>
/// 流程Id
/// </summary>
[SugarColumn(ColumnDescription = "流程Id")]
public long? FlowId { get; set; }
/// <summary>
/// 表单名称
/// </summary>
[SugarColumn(ColumnDescription = "表单名称", Length = 32)]
public string? FormName { get; set; }
/// <summary>
/// 表单类型
/// </summary>
[SugarColumn(ColumnDescription = "表单类型", Length = 32)]
public string? FormType { get; set; }
/// <summary>
/// 表单状态
/// </summary>
[SugarColumn(ColumnDescription = "表单状态", Length = 11)]
public string? FormStatus { get; set; }
/// <summary>
/// 修改前
/// </summary>
[SugarColumn(ColumnDescription = "修改前", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormBefore { get; set; }
/// <summary>
/// 修改后
/// </summary>
[SugarColumn(ColumnDescription = "修改后", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormAfter { get; set; }
/// <summary>
/// 表单结果
/// </summary>
[SugarColumn(ColumnDescription = "表单结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormResult { get; set; }
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.DataApproval;
/// <summary>
/// 审批流表单记录
/// </summary>
[SugarTable(null, "审批流表单记录")]
public class ApprovalFormRecord : EntityBaseData
{
/// <summary>
/// 流程Id
/// </summary>
[SugarColumn(ColumnDescription = "流程Id")]
public long? FlowId { get; set; }
/// <summary>
/// 表单名称
/// </summary>
[SugarColumn(ColumnDescription = "表单名称", Length = 32)]
public string? FormName { get; set; }
/// <summary>
/// 表单类型
/// </summary>
[SugarColumn(ColumnDescription = "表单类型", Length = 32)]
public string? FormType { get; set; }
/// <summary>
/// 表单状态
/// </summary>
[SugarColumn(ColumnDescription = "表单状态", Length = 11)]
public string? FormStatus { get; set; }
/// <summary>
/// 修改前
/// </summary>
[SugarColumn(ColumnDescription = "修改前", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormBefore { get; set; }
/// <summary>
/// 修改后
/// </summary>
[SugarColumn(ColumnDescription = "修改后", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormAfter { get; set; }
/// <summary>
/// 表单结果
/// </summary>
[SugarColumn(ColumnDescription = "表单结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormResult { get; set; }
}

View File

@ -4,13 +4,13 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow;
namespace Admin.NET.Plugin.DataApproval;
/// <summary>
/// 审批流程信息表
/// 数据审批信息表
/// </summary>
[SugarTable(null, "审批流程信息表")]
public class ApprovalFlow : EntityBaseData
[SugarTable(null, "数据审批信息表")]
public class DataApproval : EntityBaseData
{
/// <summary>
/// 编号

View File

@ -1,50 +1,50 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow;
/// <summary>
/// 审批流流程记录
/// </summary>
[SugarTable(null, "审批流流程记录")]
public class ApprovalFlowRecord : EntityBaseData
{
/// <summary>
/// 表单名称
/// </summary>
[SugarColumn(ColumnDescription = "表单名称", Length = 255)]
public string? FormName { get; set; }
/// <summary>
/// 表单状态
/// </summary>
[SugarColumn(ColumnDescription = "表单状态", Length = 32)]
public string? FormStatus { get; set; }
/// <summary>
/// 表单触发
/// </summary>
[SugarColumn(ColumnDescription = "表单触发", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormJson { get; set; }
/// <summary>
/// 表单结果
/// </summary>
[SugarColumn(ColumnDescription = "表单结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormResult { get; set; }
/// <summary>
/// 流程结构
/// </summary>
[SugarColumn(ColumnDescription = "流程结构", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FlowJson { get; set; }
/// <summary>
/// 流程结果
/// </summary>
[SugarColumn(ColumnDescription = "流程结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FlowResult { get; set; }
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.DataApproval;
/// <summary>
/// 审批流流程记录
/// </summary>
[SugarTable(null, "审批流流程记录")]
public class DataApprovalRecord : EntityBaseData
{
/// <summary>
/// 表单名称
/// </summary>
[SugarColumn(ColumnDescription = "表单名称", Length = 255)]
public string? FormName { get; set; }
/// <summary>
/// 表单状态
/// </summary>
[SugarColumn(ColumnDescription = "表单状态", Length = 32)]
public string? FormStatus { get; set; }
/// <summary>
/// 表单触发
/// </summary>
[SugarColumn(ColumnDescription = "表单触发", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormJson { get; set; }
/// <summary>
/// 表单结果
/// </summary>
[SugarColumn(ColumnDescription = "表单结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FormResult { get; set; }
/// <summary>
/// 流程结构
/// </summary>
[SugarColumn(ColumnDescription = "流程结构", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FlowJson { get; set; }
/// <summary>
/// 流程结果
/// </summary>
[SugarColumn(ColumnDescription = "流程结果", ColumnDataType = StaticConfig.CodeFirst_BigString)]
public string? FlowResult { get; set; }
}

View File

@ -1,20 +1,20 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
global using Admin.NET.Core;
global using Furion;
global using Furion.DependencyInjection;
global using Furion.DynamicApiController;
global using Furion.FriendlyException;
global using Mapster;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.Extensions.DependencyInjection;
global using SqlSugar;
global using System;
global using System.Collections.Generic;
global using System.ComponentModel;
global using System.ComponentModel.DataAnnotations;
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
global using Admin.NET.Core;
global using Furion;
global using Furion.DependencyInjection;
global using Furion.DynamicApiController;
global using Furion.FriendlyException;
global using Mapster;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.Extensions.DependencyInjection;
global using SqlSugar;
global using System;
global using System.Collections.Generic;
global using System.ComponentModel;
global using System.ComponentModel.DataAnnotations;
global using System.Threading.Tasks;

View File

@ -1,50 +1,50 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Plugin.ApprovalFlow.Service;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Admin.NET.Plugin.ApprovalFlow;
/// <summary>
/// 扩展审批流中间件
/// </summary>
public static class ApprovalFlowMiddlewareExtensions
{
/// <summary>
/// 使用审批流
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseApprovalFlow(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ApprovalFlowMiddleware>();
}
}
/// <summary>
/// 审批流中间件
/// </summary>
public class ApprovalFlowMiddleware
{
private readonly RequestDelegate _next;
private readonly SysApprovalService _sysApprovalService;
public ApprovalFlowMiddleware(RequestDelegate next)
{
_next = next;
_sysApprovalService = App.GetRequiredService<SysApprovalService>();
}
public async Task InvokeAsync(HttpContext context)
{
await _sysApprovalService.MatchApproval(context);
// 调用下一个中间件
await _next(context);
}
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Plugin.DataApproval.Service;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace Admin.NET.Plugin.DataApproval;
/// <summary>
/// 扩展审批流中间件
/// </summary>
public static class DataApprovalMiddlewareExtensions
{
/// <summary>
/// 使用审批流
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseDataApproval(this IApplicationBuilder builder)
{
return builder.UseMiddleware<DataApprovalMiddleware>();
}
}
/// <summary>
/// 审批流中间件
/// </summary>
public class DataApprovalMiddleware
{
private readonly RequestDelegate _next;
private readonly SysApprovalService _sysApprovalService;
public DataApprovalMiddleware(RequestDelegate next)
{
_next = next;
_sysApprovalService = App.GetRequiredService<SysApprovalService>();
}
public async Task InvokeAsync(HttpContext context)
{
await _sysApprovalService.MatchApproval(context);
// 调用下一个中间件
await _next(context);
}
}

View File

@ -4,10 +4,10 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow;
namespace Admin.NET.Plugin.DataApproval;
/// <summary>
/// 审批流程菜单表种子数据
/// 数据审批菜单表种子数据
/// </summary>
public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
{

View File

@ -1,154 +1,154 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Json;
namespace Admin.NET.Plugin.ApprovalFlow.Service;
/// <summary>
/// 审批流程服务
/// </summary>
[ApiDescriptionSettings(ApprovalFlowConst.GroupName, Order = 100, Description = "审批流程")]
public class ApprovalFlowService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<ApprovalFlow> _approvalFlowRep;
public ApprovalFlowService(SqlSugarRepository<ApprovalFlow> approvalFlowRep)
{
_approvalFlowRep = approvalFlowRep;
}
/// <summary>
/// 分页查询审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[ApiDescriptionSettings(Name = "Page")]
public async Task<SqlSugarPagedList<ApprovalFlowOutput>> Page(ApprovalFlowInput input)
{
return await _approvalFlowRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Code.Contains(input.Keyword.Trim()) || u.Name.Contains(input.Keyword.Trim()) || u.Remark.Contains(input.Keyword.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Remark), u => u.Remark.Contains(input.Remark.Trim()))
.Select<ApprovalFlowOutput>()
.ToPagedListAsync(input.Page, input.PageSize);
}
/// <summary>
/// 增加审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Add"), HttpPost]
public async Task<long> Add(AddApprovalFlowInput input)
{
var entity = input.Adapt<ApprovalFlow>();
if (input.Code == null)
{
entity.Code = await LastCode("");
}
await _approvalFlowRep.InsertAsync(entity);
return entity.Id;
}
/// <summary>
/// 更新审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Update"), HttpPost]
public async Task Update(UpdateApprovalFlowInput input)
{
var entity = input.Adapt<ApprovalFlow>();
await _approvalFlowRep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
}
/// <summary>
/// 删除审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
public async Task Delete(DeleteApprovalFlowInput input)
{
var entity = await _approvalFlowRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
await _approvalFlowRep.FakeDeleteAsync(entity); // 假删除
}
/// <summary>
/// 获取审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ApprovalFlow> GetDetail([FromQuery] QueryByIdApprovalFlowInput input)
{
return await _approvalFlowRep.GetByIdAsync(input.Id);
}
/// <summary>
/// 根据编码获取审批流信息
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<ApprovalFlow> GetInfo([FromQuery] string code)
{
return await _approvalFlowRep.GetFirstAsync(u => u.Code == code);
}
/// <summary>
/// 获取审批流列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<List<ApprovalFlowOutput>> GetList([FromQuery] ApprovalFlowInput input)
{
return await _approvalFlowRep.AsQueryable().Select<ApprovalFlowOutput>().ToListAsync();
}
/// <summary>
/// 获取今天创建的最大编号
/// </summary>
/// <param name="prefix"></param>
/// <returns></returns>
private async Task<string> LastCode(string prefix)
{
var today = DateTime.Now.Date;
var count = await _approvalFlowRep.AsQueryable().Where(u => u.CreateTime >= today).CountAsync();
return prefix + DateTime.Now.ToString("yyMMdd") + string.Format("{0:d2}", count + 1);
}
[HttpGet]
[ApiDescriptionSettings(Name = "FlowList")]
[DisplayName("获取审批流结构")]
public async Task<dynamic> FlowList([FromQuery] string code)
{
var result = await _approvalFlowRep.AsQueryable().Where(u => u.Code == code).Select<ApprovalFlowOutput>().FirstAsync();
var FlowJson = result.FlowJson != null ? JsonSerializer.Deserialize<ApprovalFlowItem>(result.FlowJson) : new ApprovalFlowItem();
var FormJson = result.FormJson != null ? JsonSerializer.Deserialize<ApprovalFormItem>(result.FormJson) : new ApprovalFormItem();
return new
{
FlowJson,
FormJson
};
}
[HttpGet]
[ApiDescriptionSettings(Name = "FormRoutes")]
[DisplayName("获取审批流规则")]
public async Task<List<string>> FormRoutes()
{
var results = await _approvalFlowRep.AsQueryable().Select<ApprovalFlowOutput>().ToListAsync();
var list = new List<string>();
foreach (var item in results)
{
var FormJson = item.FormJson != null ? JsonSerializer.Deserialize<ApprovalFormItem>(item.FormJson) : new ApprovalFormItem();
if (item.FormJson != null) list.Add(FormJson.Route);
}
return list;
}
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Json;
namespace Admin.NET.Plugin.DataApproval.Service;
/// <summary>
/// 数据审批服务
/// </summary>
[ApiDescriptionSettings(DataApprovalConst.GroupName, Order = 100, Description = "数据审批")]
public class DataApprovalService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<DataApproval> _DataApprovalRep;
public DataApprovalService(SqlSugarRepository<DataApproval> DataApprovalRep)
{
_DataApprovalRep = DataApprovalRep;
}
/// <summary>
/// 分页查询审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[ApiDescriptionSettings(Name = "Page")]
public async Task<SqlSugarPagedList<DataApprovalOutput>> Page(DataApprovalInput input)
{
return await _DataApprovalRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Code.Contains(input.Keyword.Trim()) || u.Name.Contains(input.Keyword.Trim()) || u.Remark.Contains(input.Keyword.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name.Trim()))
.WhereIF(!string.IsNullOrWhiteSpace(input.Remark), u => u.Remark.Contains(input.Remark.Trim()))
.Select<DataApprovalOutput>()
.ToPagedListAsync(input.Page, input.PageSize);
}
/// <summary>
/// 增加审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Add"), HttpPost]
public async Task<long> Add(AddDataApprovalInput input)
{
var entity = input.Adapt<DataApproval>();
if (input.Code == null)
{
entity.Code = await LastCode("");
}
await _DataApprovalRep.InsertAsync(entity);
return entity.Id;
}
/// <summary>
/// 更新审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Update"), HttpPost]
public async Task Update(UpdateDataApprovalInput input)
{
var entity = input.Adapt<DataApproval>();
await _DataApprovalRep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
}
/// <summary>
/// 删除审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
public async Task Delete(DeleteDataApprovalInput input)
{
var entity = await _DataApprovalRep.GetByIdAsync(input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
await _DataApprovalRep.FakeDeleteAsync(entity); // 假删除
}
/// <summary>
/// 获取审批流
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<DataApproval> GetDetail([FromQuery] QueryByIdDataApprovalInput input)
{
return await _DataApprovalRep.GetByIdAsync(input.Id);
}
/// <summary>
/// 根据编码获取审批流信息
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<DataApproval> GetInfo([FromQuery] string code)
{
return await _DataApprovalRep.GetFirstAsync(u => u.Code == code);
}
/// <summary>
/// 获取审批流列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<List<DataApprovalOutput>> GetList([FromQuery] DataApprovalInput input)
{
return await _DataApprovalRep.AsQueryable().Select<DataApprovalOutput>().ToListAsync();
}
/// <summary>
/// 获取今天创建的最大编号
/// </summary>
/// <param name="prefix"></param>
/// <returns></returns>
private async Task<string> LastCode(string prefix)
{
var today = DateTime.Now.Date;
var count = await _DataApprovalRep.AsQueryable().Where(u => u.CreateTime >= today).CountAsync();
return prefix + DateTime.Now.ToString("yyMMdd") + string.Format("{0:d2}", count + 1);
}
[HttpGet]
[ApiDescriptionSettings(Name = "FlowList")]
[DisplayName("获取审批流结构")]
public async Task<dynamic> FlowList([FromQuery] string code)
{
var result = await _DataApprovalRep.AsQueryable().Where(u => u.Code == code).Select<DataApprovalOutput>().FirstAsync();
var FlowJson = result.FlowJson != null ? JsonSerializer.Deserialize<DataApprovalItem>(result.FlowJson) : new DataApprovalItem();
var FormJson = result.FormJson != null ? JsonSerializer.Deserialize<ApprovalFormItem>(result.FormJson) : new ApprovalFormItem();
return new
{
FlowJson,
FormJson
};
}
[HttpGet]
[ApiDescriptionSettings(Name = "FormRoutes")]
[DisplayName("获取审批流规则")]
public async Task<List<string>> FormRoutes()
{
var results = await _DataApprovalRep.AsQueryable().Select<DataApprovalOutput>().ToListAsync();
var list = new List<string>();
foreach (var item in results)
{
var FormJson = item.FormJson != null ? JsonSerializer.Deserialize<ApprovalFormItem>(item.FormJson) : new ApprovalFormItem();
if (item.FormJson != null) list.Add(FormJson.Route);
}
return list;
}
}

View File

@ -1,27 +1,27 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Json.Serialization;
namespace Admin.NET.Plugin.ApprovalFlow.Service;
public class ApprovalFormItem
{
[JsonPropertyName("configId")]
public string ConfigId { get; set; }
[JsonPropertyName("tableName")]
public string TableName { get; set; }
[JsonPropertyName("entityName")]
public string EntityName { get; set; }
[JsonPropertyName("typeName")]
public string TypeName { get; set; }
[JsonPropertyName("route")]
public string Route => EntityName[..1].ToLower() + EntityName[1..] + "/" + TypeName;
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Json.Serialization;
namespace Admin.NET.Plugin.DataApproval.Service;
public class ApprovalFormItem
{
[JsonPropertyName("configId")]
public string ConfigId { get; set; }
[JsonPropertyName("tableName")]
public string TableName { get; set; }
[JsonPropertyName("entityName")]
public string EntityName { get; set; }
[JsonPropertyName("typeName")]
public string TypeName { get; set; }
[JsonPropertyName("route")]
public string Route => EntityName[..1].ToLower() + EntityName[1..] + "/" + TypeName;
}

View File

@ -4,12 +4,12 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow.Service;
namespace Admin.NET.Plugin.DataApproval.Service;
/// <summary>
/// 审批流输出参数
/// </summary>
public class ApprovalFlowDto
public class DataApprovalDto
{
/// <summary>
/// 主键Id

View File

@ -4,12 +4,12 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow.Service;
namespace Admin.NET.Plugin.DataApproval.Service;
/// <summary>
/// 审批流基础输入参数
/// </summary>
public class ApprovalFlowBaseInput
public class DataApprovalBaseInput
{
/// <summary>
/// 编号
@ -85,7 +85,7 @@ public class ApprovalFlowBaseInput
/// <summary>
/// 审批流分页查询输入参数
/// </summary>
public class ApprovalFlowInput : BasePageInput
public class DataApprovalInput : BasePageInput
{
/// <summary>
/// 编号
@ -106,7 +106,7 @@ public class ApprovalFlowInput : BasePageInput
/// <summary>
/// 审批流增加输入参数
/// </summary>
public class AddApprovalFlowInput : ApprovalFlowBaseInput
public class AddDataApprovalInput : DataApprovalBaseInput
{
/// <summary>
/// 软删除
@ -118,14 +118,14 @@ public class AddApprovalFlowInput : ApprovalFlowBaseInput
/// <summary>
/// 审批流删除输入参数
/// </summary>
public class DeleteApprovalFlowInput : BaseIdInput
public class DeleteDataApprovalInput : BaseIdInput
{
}
/// <summary>
/// 审批流更新输入参数
/// </summary>
public class UpdateApprovalFlowInput : ApprovalFlowBaseInput
public class UpdateDataApprovalInput : DataApprovalBaseInput
{
/// <summary>
/// 主键Id
@ -137,6 +137,6 @@ public class UpdateApprovalFlowInput : ApprovalFlowBaseInput
/// <summary>
/// 审批流主键查询输入参数
/// </summary>
public class QueryByIdApprovalFlowInput : DeleteApprovalFlowInput
public class QueryByIdDataApprovalInput : DeleteDataApprovalInput
{
}

View File

@ -1,96 +1,96 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Json.Serialization;
namespace Admin.NET.Plugin.ApprovalFlow.Service;
public class ApprovalFlowItem
{
[JsonPropertyName("nodes")]
public List<ApprovalFlowNodeItem> Nodes { get; set; }
[JsonPropertyName("edges")]
public List<ApprovalFlowEdgeItem> Edges { get; set; }
}
public class ApprovalFlowNodeItem
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("x")]
public float X { get; set; }
[JsonPropertyName("y")]
public float Y { get; set; }
[JsonPropertyName("properties")]
public FlowProperties Properties { get; set; }
[JsonPropertyName("text")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FlowTextItem Text { get; set; }
}
public class ApprovalFlowEdgeItem
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("sourceNodeId")]
public string SourceNodeId { get; set; }
[JsonPropertyName("targetNodeId")]
public string TargetNodeId { get; set; }
[JsonPropertyName("startPoint")]
public FlowEdgePointItem StartPoint { get; set; }
[JsonPropertyName("endPoint")]
public FlowEdgePointItem EndPoint { get; set; }
[JsonPropertyName("properties")]
public FlowProperties Properties { get; set; }
[JsonPropertyName("text")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FlowTextItem Text { get; set; }
[JsonPropertyName("pointsList")]
public List<FlowEdgePointItem> PointsList { get; set; }
}
public class FlowProperties
{
}
public class FlowTextItem
{
[JsonPropertyName("x")]
public float X { get; set; }
[JsonPropertyName("y")]
public float Y { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
}
public class FlowEdgePointItem
{
[JsonPropertyName("x")]
public float X { get; set; }
[JsonPropertyName("y")]
public float Y { get; set; }
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System.Text.Json.Serialization;
namespace Admin.NET.Plugin.DataApproval.Service;
public class DataApprovalItem
{
[JsonPropertyName("nodes")]
public List<DataApprovalNodeItem> Nodes { get; set; }
[JsonPropertyName("edges")]
public List<DataApprovalEdgeItem> Edges { get; set; }
}
public class DataApprovalNodeItem
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("x")]
public float X { get; set; }
[JsonPropertyName("y")]
public float Y { get; set; }
[JsonPropertyName("properties")]
public FlowProperties Properties { get; set; }
[JsonPropertyName("text")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FlowTextItem Text { get; set; }
}
public class DataApprovalEdgeItem
{
[JsonPropertyName("id")]
public string Id { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("sourceNodeId")]
public string SourceNodeId { get; set; }
[JsonPropertyName("targetNodeId")]
public string TargetNodeId { get; set; }
[JsonPropertyName("startPoint")]
public FlowEdgePointItem StartPoint { get; set; }
[JsonPropertyName("endPoint")]
public FlowEdgePointItem EndPoint { get; set; }
[JsonPropertyName("properties")]
public FlowProperties Properties { get; set; }
[JsonPropertyName("text")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public FlowTextItem Text { get; set; }
[JsonPropertyName("pointsList")]
public List<FlowEdgePointItem> PointsList { get; set; }
}
public class FlowProperties
{
}
public class FlowTextItem
{
[JsonPropertyName("x")]
public float X { get; set; }
[JsonPropertyName("y")]
public float Y { get; set; }
[JsonPropertyName("value")]
public string Value { get; set; }
}
public class FlowEdgePointItem
{
[JsonPropertyName("x")]
public float X { get; set; }
[JsonPropertyName("y")]
public float Y { get; set; }
}

View File

@ -4,12 +4,12 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.ApprovalFlow.Service;
namespace Admin.NET.Plugin.DataApproval.Service;
/// <summary>
/// 审批流输出参数
/// </summary>
public class ApprovalFlowOutput
public class DataApprovalOutput
{
/// <summary>
/// 主键Id

View File

@ -1,81 +1,81 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Http;
namespace Admin.NET.Plugin.ApprovalFlow.Service;
public class SysApprovalService : ITransient
{
private readonly SqlSugarRepository<ApprovalFlowRecord> _approvalFlowRep;
private readonly SqlSugarRepository<ApprovalFormRecord> _approvalFormRep;
private readonly ApprovalFlowService _approvalFlowService;
public SysApprovalService(SqlSugarRepository<ApprovalFlowRecord> approvalFlowRep, SqlSugarRepository<ApprovalFormRecord> approvalFormRep, ApprovalFlowService approvalFlowService)
{
_approvalFlowRep = approvalFlowRep;
_approvalFormRep = approvalFormRep;
_approvalFlowService = approvalFlowService;
}
/// <summary>
/// 匹配审批流程
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[NonAction]
public async Task MatchApproval(HttpContext context)
{
var request = context.Request;
var response = context.Response;
var path = request.Path.ToString().Split("/");
var method = request.Method;
var qs = request.QueryString;
var h = request.Headers;
var b = request.Body;
var requestHeaders = request.Headers;
var responseHeaders = response.Headers;
var serviceName = path[1];
if (serviceName.StartsWith("api"))
{
if (path.Length > 3)
{
var funcName = path[2];
var typeName = path[3];
var list = await _approvalFlowService.FormRoutes();
if (list.Any(u => u.Contains(funcName) && u.Contains(typeName)))
{
var approvalFlow = new ApprovalFlowRecord
{
FormName = funcName,
CreateTime = DateTime.Now,
};
// 判断是否需要审批
await _approvalFlowRep.InsertAsync(approvalFlow);
var approvalForm = new ApprovalFormRecord
{
FlowId = approvalFlow.Id,
FormName = funcName,
FormType = typeName,
CreateTime = DateTime.Now,
};
// 判断是否需要审批
await _approvalFormRep.InsertAsync(approvalForm);
}
}
}
await Task.CompletedTask;
}
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Http;
namespace Admin.NET.Plugin.DataApproval.Service;
public class SysApprovalService : ITransient
{
private readonly SqlSugarRepository<DataApprovalRecord> _DataApprovalRep;
private readonly SqlSugarRepository<ApprovalFormRecord> _approvalFormRep;
private readonly DataApprovalService _DataApprovalService;
public SysApprovalService(SqlSugarRepository<DataApprovalRecord> DataApprovalRep, SqlSugarRepository<ApprovalFormRecord> approvalFormRep, DataApprovalService DataApprovalService)
{
_DataApprovalRep = DataApprovalRep;
_approvalFormRep = approvalFormRep;
_DataApprovalService = DataApprovalService;
}
/// <summary>
/// 匹配数据审批
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
[NonAction]
public async Task MatchApproval(HttpContext context)
{
var request = context.Request;
var response = context.Response;
var path = request.Path.ToString().Split("/");
var method = request.Method;
var qs = request.QueryString;
var h = request.Headers;
var b = request.Body;
var requestHeaders = request.Headers;
var responseHeaders = response.Headers;
var serviceName = path[1];
if (serviceName.StartsWith("api"))
{
if (path.Length > 3)
{
var funcName = path[2];
var typeName = path[3];
var list = await _DataApprovalService.FormRoutes();
if (list.Any(u => u.Contains(funcName) && u.Contains(typeName)))
{
var DataApproval = new DataApprovalRecord
{
FormName = funcName,
CreateTime = DateTime.Now,
};
// 判断是否需要审批
await _DataApprovalRep.InsertAsync(DataApproval);
var approvalForm = new ApprovalFormRecord
{
FlowId = DataApproval.Id,
FormName = funcName,
FormType = typeName,
CreateTime = DateTime.Now,
};
// 判断是否需要审批
await _approvalFormRep.InsertAsync(approvalForm);
}
}
}
await Task.CompletedTask;
}
}

View File

@ -7,7 +7,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Admin.NET.Plugin.ApprovalFlow;
namespace Admin.NET.Plugin.DataApproval;
[AppStartup(100)]
public class Startup : AppStartup
@ -18,6 +18,6 @@ public class Startup : AppStartup
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseApprovalFlow();
app.UseDataApproval();
}
}

View File

@ -1,9 +1,9 @@
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"[openapi:GoView 大屏可视化]": {
"Group": "GoView 大屏可视化",
"Title": "GoView 大屏可视化",
"[openapi:GoView]": {
"Group": "GoView",
"Title": "GoView",
"Description": "GoView 是一个高效的拖拽式低代码数据可视化开发平台,将图表或页面元素封装为基础组件,无需编写代码即可制作数据大屏,减少心智负担。",
"Version": "2.2.8",
"Order": 95

View File

@ -24,6 +24,10 @@
</Content>
</ItemGroup>
<ItemGroup>
<Content Remove="Configuration\PaddleOCR.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="PaddleOCRSharp" Version="5.1.0" />
<PackageReference Include="Paddle.Runtime.win_x64" Version="3.1.0.1" />
@ -33,4 +37,10 @@
<ProjectReference Include="..\..\Admin.NET.Core\Admin.NET.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="Configuration\PaddleOCR.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,9 @@
{
"[openapi:PaddleOCR]": {
"Group": "PaddleOCR",
"Title": "PaddleOCR",
"Description": "文字识别与文档解析",
"Version": "1.0.0",
"Order": 100
}
}

View File

@ -10,7 +10,7 @@ namespace Admin.NET.Plugin.PaddleOCR;
/// PaddleOCR 图像识别
/// </summary>
[Const("PaddleOCR 图像识别")]
public class ApplicationConst
public class PaddleOCRConst
{
/// <summary>
/// API分组名称

View File

@ -14,7 +14,7 @@ namespace Admin.NET.Plugin.PaddleOCR.Service;
/// <summary>
/// PaddleOCR 图像识别服务 🧩
/// </summary>
[ApiDescriptionSettings(ApplicationConst.GroupName, Description = "PaddleOCR 图像识别")]
[ApiDescriptionSettings(PaddleOCRConst.GroupName, Description = "PaddleOCR 图像识别")]
public class PaddleOCRService : IDynamicApiController, ISingleton
{
private readonly PaddleOCREngine _engine;

View File

@ -1,28 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<NoWarn>1701;1702;1591;8632</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<Copyright>Admin.NET</Copyright>
<Description>Admin.NET 通用权限开发平台</Description>
</PropertyGroup>
<ItemGroup>
<Content Include="App_Data\**\*">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<PackageCopyToOutput>true</PackageCopyToOutput>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Admin.NET.Core\Admin.NET.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -1,9 +0,0 @@
{
"Admin": {
"UserName": "admin",
"Password": "ABC12345"
},
"FrontendMode": "LocalEmbedded",
"RemoteFrontend": "https://cdn.jsdelivr.net/gh/yiyungent/plugincore-admin-frontend@0.3.1/dist-cdn",
"PluginWidgetDebug": false
}

View File

@ -1 +0,0 @@
{ "EnabledPlugins": [ "Admin.NET.Plugin.Pay.Alipay" ] }

View File

@ -1,189 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Models;
using System.Net;
namespace PluginCore.AspNetCore.Controllers;
/// <summary>
/// 应用中心
/// <para>插件</para>
/// </summary>
[Route("api/plugincore/admin/[controller]/[action]")]
// [PluginCoreAdminAuthorize]
[ApiController]
[NonUnify]
public class AppCenterController : ControllerBase
{
#region Fields
private static Dictionary<string, Task> _pluginDownloadTasks;
#endregion Fields
#region Ctor
static AppCenterController()
{
_pluginDownloadTasks = new Dictionary<string, Task>();
}
public AppCenterController()
{
}
#endregion Ctor
#region Actions
#region
/// <summary>
/// 插件
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Plugins(string query = "")
{
BaseResponseModel responseDTO = new BaseResponseModel();
IList<PluginRegistryResponseModel> pluginRegistryModels = new List<PluginRegistryResponseModel>();
try
{
// 1. TODO: 从json文件中读取插件订阅源 registry url
string registryUrl = "";
// 2. TODO: 向订阅源发送 http get 获取插件列表信息 eg: http://rem-core-plugins-registry.moeci.com/?query=xxx
IList<string> remotePluginIds = new List<string>();
// 3. 根据本地已有 PluginId 插件情况 状态赋值
PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create();
// IList<string> localPluginIds = pluginConfigModel.EnabledPlugins.Concat(pluginConfigModel.DisabledPlugins).Concat(pluginConfigModel.UninstalledPlugins).ToList();
IList<string> localPluginIds = PluginPathProvider.AllPluginFolderName();
responseDTO.Code = 1;
responseDTO.Message = "获取远程插件数据成功";
responseDTO.Data = pluginRegistryModels;
}
catch (Exception ex)
{
responseDTO.Code = -1;
responseDTO.Message = "获取远程插件数据失败: " + ex.Message;
responseDTO.Data = pluginRegistryModels;
}
return await Task.FromResult(responseDTO);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> DownloadPlugin(string pluginDownloadUrl = "")
{
BaseResponseModel responseDTO = new BaseResponseModel();
#region
if (string.IsNullOrEmpty(pluginDownloadUrl))
{
responseDTO.Code = -1;
responseDTO.Message = "插件下载地址不正确";
return responseDTO;
}
// TODO: 效验是否本地已经存在相同pluginId的插件
#endregion
try
{
// 1.执行下载操作, TODO:存在问题,阻塞对性能不好,但不阻塞又不好通知用户插件下载进度,以及可能存在在插件下载过程中,用户再次点击下载
WebClient webClient = new WebClient();
// TODO: 插件下载文件路径
string pluginDownloadFilePath = "";
//webClient.DownloadFileAsync(new Uri(pluginDownloadFilePath), "");
Task task = webClient.DownloadFileTaskAsync(pluginDownloadUrl, pluginDownloadFilePath);
_pluginDownloadTasks.Add(pluginDownloadUrl, task);
webClient.DownloadFileCompleted += Plugin_DownloadFileCompleted;
webClient.DownloadProgressChanged += Plugin_DownloadProgressChanged;
webClient.Disposed += WebClient_Disposed;
responseDTO.Code = 1;
responseDTO.Message = "开始下载插件";
}
catch (Exception ex)
{
responseDTO.Code = -1;
responseDTO.Message = "下载插件失败: " + ex.Message;
}
return await Task.FromResult(responseDTO);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> DownloadPluginProgress()
{
BaseResponseModel responseDTO = new BaseResponseModel();
try
{
responseDTO.Data = new { };
responseDTO.Code = 1;
responseDTO.Message = "获取插件下载进度成功";
}
catch (Exception ex)
{
responseDTO.Code = -1;
responseDTO.Message = "获取插件下载进度失败: " + ex.Message;
}
return await Task.FromResult(responseDTO);
}
#endregion
#endregion Actions
#region Helpers
/// <summary>
/// 插件下载完成
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Plugin_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
Console.WriteLine("插件下载完成");
// 1.从 _pluginDownloadTasks 中移除
//_pluginDownloadTasks.Remove();
// 2. 解压插件
}
private void Plugin_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
Console.WriteLine($"插件下载进度改变: {e.ProgressPercentage}% {e.BytesReceived}/{e.TotalBytesToReceive}");
}
private void WebClient_Disposed(object sender, EventArgs e)
{
if (sender is WebClient webClient)
{
Console.WriteLine(webClient.BaseAddress);
}
Console.WriteLine(nameof(WebClient_Disposed) + ": " + sender.ToString());
}
#endregion Helpers
}

View File

@ -1,268 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.AspNetCore.Extensions;
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Interfaces;
using System.Runtime.Loader;
namespace PluginCore.AspNetCore.Controllers;
/// <summary>
/// [ASP.NET Core — 依赖注入\_啊晚的博客-CSDN博客\_asp.net core 依赖注入](https://blog.csdn.net/weixin_37648525/article/details/127942292)
/// [ASP.NET Core中的依赖注入3: 服务的注册与提供 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-register.html)
/// [ASP.NET Core中的依赖注入5: ServiceProvider实现揭秘 【总体设计 】 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-service-provider-1.html)
/// [dotnet/ServiceProvider.cs at main · dotnet/dotnet](https://github.com/dotnet/dotnet/blob/main/src/runtime/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs)
/// [Net6 DI源码分析Part2 Engine,ServiceProvider - 一身大膘 - 博客园](https://www.cnblogs.com/hts92/p/15800990.html)
/// [【特别的骚气】asp.net core运行时注入服务实现类库热插拔 - 四处观察 - 博客园](https://www.cnblogs.com/1996-Chinese-Chen/p/16154218.html)
///
/// ActivatorUtilities.CreateInstance<PluginCore.IPlugins.IPlugin>(serviceProvider, "test");
/// ActivatorUtilities.GetServiceOrCreateInstance<PluginCore.IPlugins.IPlugin>(serviceProvider);
/// </summary>
[Route("api/plugincore/admin/[controller]/[action]")]
//[PluginCoreAdminAuthorize]
[ApiController]
[NonUnify]
public class DebugController : ControllerBase
{
#region Fields
private readonly IPluginContextManager _pluginContextManager;
#endregion Fields
#region Ctor
public DebugController(IPluginContextManager pluginContextManager)
{
_pluginContextManager = pluginContextManager;
}
#endregion Ctor
#region Actions
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> PluginContexts()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
var pluginContextList = _pluginContextManager.All();
Dictionary<string, List<string>> keyValuePairs = new Dictionary<string, List<string>>();
foreach (var pluginContext in pluginContextList)
{
keyValuePairs.Add($"{pluginContext.GetType().ToString()} - {pluginContext.PluginId} - {pluginContext.GetHashCode()}", pluginContext.Assemblies.Select(m => m.FullName).ToList());
}
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = keyValuePairs;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> AssemblyLoadContexts()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
var assemblyLoadContextDefault = AssemblyLoadContext.Default;
var assemblyLoadContextAll = AssemblyLoadContext.All;
var responseDataModel = new AssemblyLoadContextsResponseDataModel();
responseDataModel.Default = new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel
{
Name = assemblyLoadContextDefault.Name,
Type = assemblyLoadContextDefault.GetType().ToString(),
Assemblies = assemblyLoadContextDefault.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList()
};
responseDataModel.All = assemblyLoadContextAll.Select(item => new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel
{
Name = item.Name,
Type = item.GetType().ToString(),
Assemblies = item.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList()
}).ToList();
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = responseDataModel;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Assemblies()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
List<AssemblyModel> assemblyModels = new List<AssemblyModel>();
foreach (var item in assemblies)
{
assemblyModels.Add(new AssemblyModel
{
FullName = item.FullName,
DefinedTypes = item.DefinedTypes.Select(m => m.FullName).ToList()
});
}
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = assemblyModels;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Services([FromServices] IServiceProvider serviceProvider)
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
//IServiceProvider serviceProvider = HttpContext.RequestServices;
//var provider = serviceProvider.GetType().GetProperty("RootProvider", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
//var serviceField = provider.GetType().GetField("_realizedServices", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
//var serviceValue = serviceField.GetValue(provider);
//var funcType = serviceField.FieldType.GetGenericArguments()[1].GetGenericArguments()[0];
//ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object?>> realizedServices = (ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object?>>)serviceValue;
// 获取所有已经注册的服务
var allService = serviceProvider.GetAllServiceDescriptors();
List<ServiceModel> serviceModels = new List<ServiceModel>();
foreach (var item in allService)
{
serviceModels.Add(new ServiceModel
{
Type = item.Key.ToString(),
ImplementationType = item.Value.ImplementationType?.ToString() ?? "",
Lifetime = item.Value.Lifetime.ToString(),
TypeAssembly = new AssemblyModel
{
FullName = item.Key.Assembly.FullName,
},
ImplementationTypeAssembly = new AssemblyModel
{
FullName = item.Value.ImplementationType?.Assembly?.FullName ?? ""
}
});
}
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = serviceModels;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
#endregion Actions
public sealed class AssemblyLoadContextsResponseDataModel
{
public AssemblyLoadContextModel Default
{
get; set;
}
public List<AssemblyLoadContextModel> All
{
get; set;
}
public sealed class AssemblyLoadContextModel
{
public string Name
{
get; set;
}
public string Type
{
get; set;
}
public List<AssemblyModel> Assemblies
{
get; set;
}
}
}
public sealed class AssembliesResponseDataModel
{
}
public sealed class ServiceModel
{
public string Type
{
get; set;
}
public string ImplementationType
{
get; set;
}
public string Lifetime
{
get; set;
}
public AssemblyModel TypeAssembly
{
get; set;
}
public AssemblyModel ImplementationTypeAssembly
{
get; set;
}
}
public sealed class AssemblyModel
{
public string FullName
{
get; set;
}
public List<string> DefinedTypes
{
get; set;
}
}
}

View File

@ -1,90 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Interfaces;
using PluginCore.IPlugins;
namespace PluginCore.AspNetCore.Controllers;
[Route("api/plugincore/[controller]/[action]")]
[ApiController]
[NonUnify]
public class PluginWidgetController : ControllerBase
{
#region Fields
private readonly IPluginFinder _pluginFinder;
#endregion Fields
#region Ctor
public PluginWidgetController(IPluginFinder pluginFinder)
{
_pluginFinder = pluginFinder;
}
#endregion Ctor
#region Actions
#region Widget
/// <summary>
/// Widget
/// </summary>
/// <returns></returns>
[HttpGet, HttpPost]
public async Task<ActionResult> Widget(string widgetKey, string extraPars = "")
{
BaseResponseModel responseModel = new ResponseModel.BaseResponseModel();
string responseData = "";
widgetKey = widgetKey.Trim('"', '\'');
string[] extraParsArr = null;
if (!string.IsNullOrEmpty(extraPars))
{
extraParsArr = extraPars.Split(",", StringSplitOptions.RemoveEmptyEntries);
extraParsArr = extraParsArr.Select(m => m.Trim('"', '\'')).ToArray();
}
StringBuilder sb = new StringBuilder();
sb.AppendLine($"<!-- start:PluginCore.IPlugins.IWidgetPlugin.Widget({widgetKey},{extraPars}) -->");
try
{
List<IWidgetPlugin> plugins = this._pluginFinder.EnablePlugins<IWidgetPlugin>().ToList();
foreach (var item in plugins)
{
string widgetStr = await item.Widget(widgetKey, extraParsArr);
if (!string.IsNullOrEmpty(widgetStr))
{
// TODO: 配合 PluginCoreConfig.PluginWidgetDebug
// TODO: PluginCoreConfig 改为 Options 模式, 避免手动反复读取文件 效率低
//sb.AppendLine($"<!-- {item.GetType().ToString()}: -->");
sb.AppendLine(widgetStr);
}
}
}
catch (Exception ex)
{
Utils.LogUtil.Error<PluginWidgetController>(ex.ToString());
sb.AppendLine($"<!-- Exception: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, Details: Console -->");
}
sb.AppendLine($"<!-- end:PluginCore.IPlugins.IWidgetPlugin.Widget({widgetKey},{extraPars}) -->");
responseData = sb.ToString();
responseModel.Code = 1;
responseModel.Message = "Load Widget Success";
responseModel.Data = responseData;
//return await Task.FromResult(responseModel);
return Content(responseData, "text/html;charset=utf-8");
}
#endregion Widget
#endregion Actions
}

View File

@ -1,709 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.AspNetCore.Interfaces;
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Infrastructure;
using PluginCore.Interfaces;
using PluginCore.IPlugins;
using PluginCore.Models;
namespace PluginCore.AspNetCore.Controllers;
[Route("api/plugincore/admin/[controller]/[action]")]
// [PluginCoreAdminAuthorize]
[ApiController]
[NonUnify]
public class PluginsController : ControllerBase
{
#region Fields
private readonly IPluginManager _pluginManager;
private readonly IPluginFinder _pluginFinder;
private readonly IPluginApplicationBuilderManager _pluginApplicationBuilderManager;
#endregion Fields
#region Ctor
public PluginsController(IPluginManager pluginManager, IPluginFinder pluginFinder, IPluginApplicationBuilderManager pluginApplicationBuilderManager)
{
_pluginManager = pluginManager;
_pluginFinder = pluginFinder;
_pluginApplicationBuilderManager = pluginApplicationBuilderManager;
}
#endregion Ctor
#region Actions
#region
/// <summary>
/// 加载插件列表
/// </summary>
/// <param name="status">插件状态</param>
/// <returns></returns>
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> List(string status = "all")
{
BaseResponseModel responseData = new ResponseModel.BaseResponseModel();
var pluginConfigModel = PluginConfigModelFactory.Create();
// 获取所有插件信息
IList<PluginInfoModel> pluginInfoModels = PluginInfoModelFactory.CreateAll();
IList<PluginInfoResponseModel> responseModels = new List<PluginInfoResponseModel>();
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
// 添加插件状态
responseModels = PluginInfoModelToResponseModel(pluginInfoModels, pluginConfigModel, enablePluginIds);
#region
switch (status.ToLower())
{
case "all":
break;
case "enabled":
responseModels = responseModels.Where(m => m.Status == PluginStatus.Enabled).ToList();
break;
case "disabled":
responseModels = responseModels.Where(m => m.Status == PluginStatus.Disabled).ToList();
break;
default:
break;
}
#endregion
responseData.Code = 1;
responseData.Message = "加载插件列表成功";
responseData.Data = responseModels;
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Uninstall(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
pluginId = pluginId.Trim();
var pluginConfigModel = PluginConfigModelFactory.Create();
// 卸载插件 必须 先禁用插件
#region
if (pluginConfigModel.EnabledPlugins.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = "卸载失败: 请先禁用此插件";
return await Task.FromResult(responseData);
}
string pluginDirStr = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId);
string pluginWwwrootDirStr = Path.Combine(PluginPathProvider.PluginsWwwRootDir(), pluginId);
if (!Directory.Exists(pluginDirStr) && !Directory.Exists(pluginWwwrootDirStr))
{
responseData.Code = -2;
responseData.Message = "卸载失败: 此插件不存在";
return await Task.FromResult(responseData);
}
#endregion
try
{
// PS:卸载插件必须先禁用插件所以此时插件LoadContext已被移除释放(插件Assemblies已被释放), 此处不需移除LoadContext
// 1.删除物理文件
var pluginDir = new DirectoryInfo(pluginDirStr);
if (pluginDir.Exists)
{
pluginDir.Delete(true);
}
// 虽然 已禁用 时 pluginWwwrootDirStr/pluginId 已删除, 但为确保, 还是再删除一次
var pluginWwwrootDir = new DirectoryInfo(pluginWwwrootDirStr);
if (pluginWwwrootDir.Exists)
{
pluginWwwrootDir.Delete(true);
}
responseData.Code = 1;
responseData.Message = "卸载成功";
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "卸载失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Enable(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
var pluginConfigModel = PluginConfigModelFactory.Create();
// 效验是否存在于 已禁用插件列表
#region
pluginId = pluginId.Trim();
var pluginDir = new DirectoryInfo(Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId));
if (pluginDir != null && !pluginDir.Exists)
{
responseData.Code = -1;
responseData.Message = "启用失败: 此插件不存在";
return await Task.FromResult(responseData);
}
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
if (enablePluginIds.Contains(pluginId))
{
responseData.Code = -2;
responseData.Message = "启用失败: 此插件已启用";
return await Task.FromResult(responseData);
}
#endregion
try
{
// 1. 创建插件程序集加载上下文, 添加到 PluginsLoadContexts
_pluginManager.LoadPlugin(pluginId);
// 2. 添加到 pluginConfigModel.EnabledPlugins
pluginConfigModel.EnabledPlugins.Add(pluginId);
// 4.保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
// 5. 找到此插件实例
IPlugin plugin = _pluginFinder.Plugin(pluginId);
if (plugin == null)
{
// 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json
try
{
_pluginManager.UnloadPlugin(pluginId);
}
catch (Exception ex)
{ }
// 从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
responseData.Code = -1;
responseData.Message = "启用失败: 此插件不存在";
return await Task.FromResult(responseData);
}
// 6.调取插件的 AfterEnable(), 插件开发者可在此回收资源
var pluginEnableResult = plugin.AfterEnable();
if (!pluginEnableResult.IsSuccess)
{
// 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json
try
{
_pluginManager.UnloadPlugin(pluginId);
}
catch (Exception ex)
{ }
// 从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
responseData.Code = -1;
responseData.Message = "启用失败: 来自插件的错误信息: " + pluginEnableResult.Message;
return await Task.FromResult(responseData);
}
// 7. ReBuild
this._pluginApplicationBuilderManager.ReBuild();
// 8. 尝试复制 插件下的 wwwroot 到 Plugins_wwwroot
string wwwRootDir = PluginPathProvider.WwwRootDir(pluginId);
if (Directory.Exists(wwwRootDir))
{
string targetDir = PluginPathProvider.PluginWwwRootDir(pluginId);
Utils.FileUtil.CopyFolder(wwwRootDir, targetDir);
}
responseData.Code = 1;
responseData.Message = "启用成功";
}
catch (Exception ex)
{
responseData.Code = -2;
responseData.Message = "启用失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Disable(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
#region
pluginId = pluginId.Trim();
var pluginConfigModel = PluginConfigModelFactory.Create();
// string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
// // 效验是否存在于 已启用插件列表
// if (!enablePluginIds.Contains(pluginId))
// {
// responseData.Code = -1;
// responseData.Message = "禁用失败: 此插件不存在, 或未启用";
// return await Task.FromResult(responseData);
// }
#endregion
try
{
// 1. 找到此插件实例
IPlugin plugin = _pluginFinder.Plugin(pluginId);
if (plugin == null)
{
responseData.Code = -1;
responseData.Message = "禁用失败: 此插件不存在, 或未启用";
return await Task.FromResult(responseData);
}
try
{
// 2.调取插件的 BeforeDisable(), 插件开发者可在此回收资源
var pluginDisableResult = plugin.BeforeDisable();
if (!pluginDisableResult.IsSuccess)
{
responseData.Code = -1;
responseData.Message = "禁用失败: 来自插件的错误信息: " + pluginDisableResult.Message;
return await Task.FromResult(responseData);
}
// 3.移除插件对应的程序集加载上下文
_pluginManager.UnloadPlugin(pluginId);
// 3.1. ReBuild
this._pluginApplicationBuilderManager.ReBuild();
if (pluginConfigModel.EnabledPlugins.Contains(pluginId))
{
// 4.从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 5.保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
}
}
catch (Exception ex)
{
//Utils.LogUtil.Error(ex.ToString());
responseData.Code = -1;
responseData.Message = "禁用失败: 此插件不存在, 或未启用";
return await Task.FromResult(responseData);
}
// 7. 尝试移除 Plugins_wwwroot/PluginId
string pluginWwwRootDir = PluginPathProvider.PluginWwwRootDir(pluginId);
if (Directory.Exists(pluginWwwRootDir))
{
Directory.Delete(pluginWwwRootDir, true);
}
responseData.Code = 1;
responseData.Message = "禁用成功";
}
catch (Exception ex)
{
responseData.Code = -2;
responseData.Message = "禁用失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
/// <summary>
/// 上传插件
/// </summary>
/// <param name="file">注意: 参数名一定为 file 对应前端传过来时以 file 为名</param>
/// <returns></returns>
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Upload(IFormFile file)
{
BaseResponseModel responseData = new BaseResponseModel();
#region
if (file == null)
{
responseData.Code = -1;
responseData.Message = "上传的文件不能为空";
return responseData;
}
//文件后缀
string fileExtension = Path.GetExtension(file.FileName);//获取文件格式,拓展名
// 类型标记
UploadFileType uploadFileType = UploadFileType.NoAllowedType;
switch (fileExtension)
{
case ".zip":
uploadFileType = UploadFileType.Zip;
break;
case ".nupkg":
uploadFileType = UploadFileType.Nupkg;
break;
}
if (fileExtension != ".zip" && fileExtension != ".nupkg")
{
responseData.Code = -1;
// nupkg 其实就是 zip
responseData.Message = "只能上传 zip 或 nupkg 格式文件";
return responseData;
}
// PluginCore.AspNetCore-v1.0.2 起 不再限制插件上传大小
//判断文件大小
//var fileSize = file.Length;
//if (fileSize > 1024 * 1024 * 5) // 5M
//{
// responseData.Code = -1;
// responseData.Message = "上传的文件不能大于5MB";
// return responseData;
//}
#endregion
try
{
// 1.先上传到 临时插件上传目录, 用Guid.zip作为保存文件名
string tempZipFilePath = Path.Combine(PluginPathProvider.TempPluginUploadDir(), Guid.NewGuid() + ".zip");
using (var fs = System.IO.File.Create(tempZipFilePath))
{
file.CopyTo(fs); //将上传的文件文件流复制到fs中
fs.Flush();//清空文件流
}
// 2.解压
bool isDecomparessSuccess = false;
if (uploadFileType == UploadFileType.Zip)
{
isDecomparessSuccess = Utils.ZipHelper.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", ""));
}
else if (uploadFileType == UploadFileType.Nupkg)
{
isDecomparessSuccess = NupkgService.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", ""));
}
// 3.删除原压缩包
System.IO.File.Delete(tempZipFilePath);
if (!isDecomparessSuccess)
{
responseData.Code = -1;
responseData.Message = "解压插件压缩包失败";
return responseData;
}
// 4.读取其中的info.json, 获取 PluginId 值
PluginInfoModel pluginInfoModel = PluginInfoModelFactory.ReadPluginDir(tempZipFilePath.Replace(".zip", ""));
if (pluginInfoModel == null || string.IsNullOrEmpty(pluginInfoModel.PluginId))
{
// 记得删除已不再需要的临时插件文件夹
Directory.Delete(tempZipFilePath.Replace(".zip", ""), true);
responseData.Code = -1;
responseData.Message = "不合法的插件";
return responseData;
}
string pluginId = pluginInfoModel.PluginId;
// 5.检索 此 PluginId 是否本地插件已存在
var pluginConfigModel = PluginConfigModelFactory.Create();
// 本地已经存在的 PluginId
IList<string> localExistPluginIds = PluginPathProvider.AllPluginFolderName();
if (localExistPluginIds.Contains(pluginId))
{
// 记得删除已不再需要的临时插件文件夹
Directory.Delete(tempZipFilePath.Replace(".zip", ""), true);
responseData.Code = -1;
responseData.Message = $"本地已有此插件 (PluginId: {pluginId}), 请前往插件列表删除后, 再上传";
return responseData;
}
// 6.本地无此插件 -> 移动插件文件夹到 Plugins 下, 并以 PluginId 为插件文件夹名
string pluginsRootPath = PluginPathProvider.PluginsRootPath();
string newPluginDir = Path.Combine(pluginsRootPath, pluginId);
Directory.Move(tempZipFilePath.Replace(".zip", ""), newPluginDir);
// 7. 放入 Plugins 中, 默认为 已禁用
responseData.Code = 1;
responseData.Message = $"上传插件成功 (PluginId: {pluginId})";
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "上传插件失败: " + ex.Message;
ex = ex.InnerException;
while (ex != null)
{
responseData.Message += " - " + ex.InnerException.Message;
ex = ex.InnerException;
}
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Details(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
pluginId = pluginId.Trim();
var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = $"查看详细失败: 不存在 {pluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
PluginInfoModel pluginInfoModel = PluginInfoModelFactory.Create(pluginId);
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
PluginInfoResponseModel pluginInfoResponseModel = PluginInfoModelToResponseModel(new List<PluginInfoModel>() { pluginInfoModel }, pluginConfigModel, enablePluginIds).FirstOrDefault();
responseData.Code = 1;
responseData.Message = "查看详细成功";
responseData.Data = pluginInfoResponseModel;
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "查看详细失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Readme(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
pluginId = pluginId.Trim();
// var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = $"查看文档失败: 不存在 {pluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
PluginReadmeModel readmeModel = PluginReadmeModelFactory.Create(pluginId);
PluginReadmeResponseModel readmeResponseModel = new PluginReadmeResponseModel();
readmeResponseModel.Content = readmeModel?.Content ?? "";
readmeResponseModel.PluginId = pluginId;
responseData.Code = 1;
responseData.Message = "查看文档成功";
responseData.Data = readmeResponseModel;
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "查看文档失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet]
public async Task<ActionResult<BaseResponseModel>> Settings(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
pluginId = pluginId.Trim();
// var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = $"查看设置失败: 不存在 {pluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
string settingsJsonStr = PluginSettingsModelFactory.Create(pluginId);
responseData.Code = 1;
responseData.Message = "查看设置成功";
responseData.Data = settingsJsonStr ?? "无设置项";
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "查看设置失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
[HttpPost]
public async Task<ActionResult<BaseResponseModel>> Settings(PluginSettingsInputModel inputModel)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
inputModel.PluginId = inputModel.PluginId.Trim();
// var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(inputModel.PluginId))
{
responseData.Code = -1;
responseData.Message = $"设置失败: 不存在 {inputModel.PluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
inputModel.Data = inputModel.Data ?? "";
PluginSettingsModelFactory.Save(pluginSettingsJsonStr: inputModel.Data, pluginId: inputModel.PluginId);
responseData.Code = 1;
responseData.Message = "设置成功";
responseData.Data = inputModel.Data;
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "设置失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#endregion Actions
#region Helpers
[NonAction]
private IList<PluginInfoResponseModel> PluginInfoModelToResponseModel(IList<PluginInfoModel> pluginInfoModels, PluginConfigModel pluginConfigModel, string[] enablePluginIds)
{
// 获取 Plugins 下所有插件
// DirectoryInfo pluginsDir = new DirectoryInfo(PluginPathProvider.PluginsRootPath());
// List<string> pluginIds = pluginsDir?.GetDirectories()?.Select(m => m.Name)?.ToList() ?? new List<string>();
IList<PluginInfoResponseModel> responseModels = new List<PluginInfoResponseModel>();
#region
foreach (var model in pluginInfoModels)
{
PluginInfoResponseModel responseModel = new PluginInfoResponseModel();
responseModel.Author = model.Author;
responseModel.Description = model.Description;
responseModel.DisplayName = model.DisplayName;
responseModel.PluginId = model.PluginId;
responseModel.SupportedVersions = model.SupportedVersions;
responseModel.Version = model.Version;
responseModel.DependPlugins = model.DependPlugins;
if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && !enablePluginIds.Contains(model.PluginId))
{
// 错误情况: 配置 标识 已启用, 但实际没有启用成功
pluginConfigModel.EnabledPlugins.Remove(model.PluginId);
PluginConfigModelFactory.Save(pluginConfigModel);
responseModel.Status = PluginStatus.Disabled;
}
else if (!pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId))
{
// 错误情况: 配置没有标识 已启用, 但实际 已启用
pluginConfigModel.EnabledPlugins.Add(model.PluginId);
PluginConfigModelFactory.Save(pluginConfigModel);
responseModel.Status = PluginStatus.Enabled;
}
else if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId))
{
responseModel.Status = PluginStatus.Enabled;
}
else
{
responseModel.Status = PluginStatus.Disabled;
}
responseModels.Add(responseModel);
}
#endregion
return responseModels;
}
public enum UploadFileType
{
NoAllowedType = 0,
Zip = 1,
Nupkg = 2
}
#endregion Helpers
}

View File

@ -1,181 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Furion.DataEncryption;
using Microsoft.AspNetCore.Authorization;
using PluginCore.AspNetCore.RequestModel.User;
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Config;
namespace PluginCore.AspNetCore.Controllers;
[Route("api/plugincore/admin/[controller]/[action]")]
[ApiController]
[NonUnify]
public class UserController : ControllerBase
{
public string RemoteFronted
{
get
{
return PluginCore.Config.PluginCoreConfigFactory.Create().RemoteFrontend;
}
}
private readonly UserManager _userManager;
private readonly SqlSugarRepository<SysUser> _sysUserRep;
private readonly SysOrgService _sysOrgService;
private readonly SysUserExtOrgService _sysUserExtOrgService;
private readonly SysUserRoleService _sysUserRoleService;
private readonly SysConfigService _sysConfigService;
public UserController(UserManager userManager,
SqlSugarRepository<SysUser> sysUserRep,
SysOrgService sysOrgService,
SysUserExtOrgService sysUserExtOrgService,
SysUserRoleService sysUserRoleService,
SysConfigService sysConfigService)
{
_userManager = userManager;
_sysUserRep = sysUserRep;
_sysOrgService = sysOrgService;
_sysUserExtOrgService = sysUserExtOrgService;
_sysUserRoleService = sysUserRoleService;
_sysConfigService = sysConfigService;
}
/// <summary>
/// 登录系统
/// </summary>
/// <param name="input"></param>
/// <remarks>用户名/密码superadmin/123456</remarks>
/// <returns></returns>
[AllowAnonymous]
[HttpGet, HttpPost]
[DisplayName("登录系统")]
public async Task<ActionResult<BaseResponseModel>> Login([FromBody] LoginRequestModel input)
{
BaseResponseModel responseModel = new BaseResponseModel();
// 账号是否存在
var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).Filter(null, true).FirstAsync(u => u.Account.Equals(input.UserName));
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 账号是否被冻结
if (user.Status == StatusEnum.Disable)
throw Oops.Oh(ErrorCodeEnum.D1017);
// 租户是否被禁用
var tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Id == user.TenantId);
if (tenant != null && tenant.Status == StatusEnum.Disable)
throw Oops.Oh(ErrorCodeEnum.Z1003);
// 密码是否正确
if (CryptogramUtil.CryptoType == CryptogramEnum.MD5.ToString())
{
if (user.Password != MD5Encryption.Encrypt(input.Password))
throw Oops.Oh(ErrorCodeEnum.D1000);
}
else
{
if (CryptogramUtil.Decrypt(user.Password) != input.Password)
throw Oops.Oh(ErrorCodeEnum.D1000);
}
var tokenExpire = await _sysConfigService.GetTokenExpire();
var refreshTokenExpire = await _sysConfigService.GetRefreshTokenExpire();
// 生成Token令牌
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
{
{ ClaimConst.UserId, user.Id },
{ ClaimConst.TenantId, user.TenantId },
{ ClaimConst.Account, user.Account },
{ ClaimConst.RealName, user.RealName },
{ ClaimConst.AccountType, user.AccountType },
{ ClaimConst.OrgId, user.OrgId },
{ ClaimConst.OrgName, user.SysOrg?.Name },
//{ ClaimConst.OrgType, user.SysOrg?.OrgType },
}, tokenExpire);
// 生成刷新Token令牌
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, refreshTokenExpire);
responseModel.Code = 1;
responseModel.Message = "登录成功";
responseModel.Data = new
{
token = accessToken,
userName = user.NickName,
RefreshToken = refreshToken
};
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Logout()
{
BaseResponseModel responseModel = new BaseResponseModel()
{
Code = 1,
Message = "退出登录成功"
};
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Info()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
string adminUserName = PluginCoreConfigFactory.Create().Admin.UserName;
responseModel.Code = 1;
responseModel.Message = "成功";
responseModel.Data = new
{
name = adminUserName,
//avatar = this.RemoteFronted + "/images/avatar.gif"
avatar = ""
};
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "失败: " + ex.Message;
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Update([FromBody] UpdateRequestModel requestModel)
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
PluginCoreConfig pluginCoreConfig = PluginCoreConfigFactory.Create();
pluginCoreConfig.Admin.UserName = requestModel.UserName;
pluginCoreConfig.Admin.Password = requestModel.Password;
PluginCoreConfigFactory.Save(pluginCoreConfig);
responseModel.Code = 1;
responseModel.Message = "修改成功, 需要重新登录";
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "失败: " + ex.Message;
}
return await Task.FromResult(responseModel);
}
}

View File

@ -1,69 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.PluginCore;
/// <summary>
/// 系统动态插件表
/// </summary>
[SugarTable(null, "系统动态插件表")]
[SysTable]
public class SysPluginCore : EntityTenant
{
/// <summary>
/// 插件ID
/// </summary>
[SugarColumn(ColumnDescription = "插件ID", Length = 128)]
[Required]
public virtual string PluginId { get; set; }
/// <summary>
/// 名称
/// </summary>
[SugarColumn(ColumnDescription = "名称", Length = 64)]
[Required, MaxLength(64)]
public virtual string DisplayName { get; set; }
/// <summary>
/// 作者
/// </summary>
[SugarColumn(ColumnDescription = "作者", Length = 64)]
[Required, MaxLength(64)]
public virtual string Author { get; set; }
/// <summary>
/// 版本
/// </summary>
[SugarColumn(ColumnDescription = "版本", Length = 64)]
[Required, MaxLength(64)]
public virtual string Version { get; set; }
/// <summary>
/// 描述
/// </summary>
[SugarColumn(ColumnDescription = "描述", Length = 512)]
[MaxLength(512)]
public string? Description { get; set; }
/// <summary>
/// 排序
/// </summary>
[SugarColumn(ColumnDescription = "排序")]
public int OrderNo { get; set; } = 100;
/// <summary>
/// 状态
/// </summary>
[SugarColumn(ColumnDescription = "状态")]
public StatusEnum Status { get; set; } = StatusEnum.Enable;
/// <summary>
/// 备注
/// </summary>
[SugarColumn(ColumnDescription = "备注", Length = 128)]
[MaxLength(128)]
public string? Remark { get; set; }
}

View File

@ -1,23 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
global using Admin.NET.Core;
global using Admin.NET.Core.Service;
global using Furion;
global using Furion.DependencyInjection;
global using Furion.DynamicApiController;
global using Furion.FriendlyException;
global using Mapster;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.Extensions.DependencyInjection;
global using Newtonsoft.Json;
global using SqlSugar;
global using System.ComponentModel;
global using System.ComponentModel.DataAnnotations;
global using System.Data;
global using System.Linq.Dynamic.Core;
global using System.Text;

View File

@ -1,32 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.PluginCore;
/// <summary>
/// 系统菜单表种子数据
/// </summary>
[IncreSeed]
public class SysMenu_PluginCore_SeedData : ISqlSugarEntitySeedData<SysMenu>
{
/// <summary>
/// 种子数据
/// </summary>
/// <returns></returns>
public IEnumerable<SysMenu> HasData()
{
return
[
new SysMenu{ Id=1310000000802, Pid=1310000000301, Title="应用插件", Path="/platform/pluginCore", Name="sysPluginCore", Component="/system/pluginCore/index", Icon="ele-Connection", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=210 },
new SysMenu{ Id=1310000000803, Pid=1310000000802, Title="启用", Permission="sysPluginCore/enable", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
new SysMenu{ Id=1310000000804, Pid=1310000000802, Title="卸载", Permission="sysPluginCore/delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=110 },
new SysMenu{ Id=1310000000805, Pid=1310000000802, Title="禁用", Permission="sysPluginCore/disable", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 },
new SysMenu{ Id=1310000000806, Pid=1310000000802, Title="详细", Permission="sysPluginCore/details", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 },
new SysMenu{ Id=1310000000807, Pid=1310000000802, Title="文档", Permission="sysPluginCore/readme", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=140 },
new SysMenu{ Id=1310000000808, Pid=1310000000802, Title="设置", Permission="sysPluginCore/setting", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=150 },
];
}
}

View File

@ -1,41 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Admin.NET.Plugin.PluginCore;
public class PagePluginCoreInput : BasePageInput
{
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 编码
/// </summary>
public string Code { get; set; }
}
public class AddPluginCoreInput : SysPluginCore
{
}
public class UpdatePluginCoreInput : AddPluginCoreInput
{
}
public class DeletePluginCoreInput : BaseIdInput
{
}
public class EnablePluginCoreInput : BaseIdInput
{
}
public class UpdatePluginCoreSettingInput : BaseIdInput
{
public string Data { get; set; }
}

View File

@ -1,695 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore;
using PluginCore.AspNetCore.Interfaces;
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Infrastructure;
using PluginCore.Interfaces;
using PluginCore.IPlugins;
using PluginCore.Models;
using PluginCore.Utils;
namespace Admin.NET.Plugin.PluginCore;
/// <summary>
/// 系统动态插件服务
/// </summary>
[ApiDescriptionSettings(Order = 245)]
public class SysPluginCoreService : IDynamicApiController, ITransient
{
#region Fields
private readonly IPluginManager _pluginManager;
private readonly IPluginFinder _pluginFinder;
private readonly IPluginApplicationBuilderManager _pluginApplicationBuilderManager;
#endregion Fields
private readonly IDynamicApiRuntimeChangeProvider _provider;
private readonly SqlSugarRepository<SysPluginCore> _sysPluginRep;
public SysPluginCoreService(IPluginManager pluginManager, IPluginFinder pluginFinder, IPluginApplicationBuilderManager pluginApplicationBuilderManager, IDynamicApiRuntimeChangeProvider provider,
SqlSugarRepository<SysPluginCore> sysPluginRep)
{
_pluginManager = pluginManager;
_pluginFinder = pluginFinder;
_pluginApplicationBuilderManager = pluginApplicationBuilderManager;
_provider = provider;
_sysPluginRep = sysPluginRep;
}
/// <summary>
/// 获取动态插件列表
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取动态插件列表")]
public async Task<SqlSugarPagedList<SysPluginCore>> Page(PagePluginInput input)
{
return await _sysPluginRep.AsQueryable()
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.DisplayName.Contains(input.Name))
.OrderBy(u => u.OrderNo)
.ToPagedListAsync(input.Page, input.PageSize);
}
/// <summary>
/// 查看详细
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Details"), HttpGet]
[DisplayName("查看详细")]
public async Task<PluginInfoResponseModel> Details(long id)
{
var pluginCore = await _sysPluginRep.GetByIdAsync(id);
if (pluginCore == null) throw Oops.Oh("查询插件ID失败"); ;
// 先移除再添加动态程序集/接口
try
{
#region
var pluginId = pluginCore.PluginId;
var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
throw Oops.Oh($"查看详细失败: 不存在 {pluginId} 插件");
}
#endregion
PluginInfoModel pluginInfoModel = PluginInfoModelFactory.Create(pluginId);
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
PluginInfoResponseModel pluginInfoResponseModel = PluginInfoModelToResponseModel(new List<PluginInfoModel>() { pluginInfoModel }, pluginConfigModel, enablePluginIds).FirstOrDefault();
return pluginInfoResponseModel;
}
catch (Exception ex)
{
throw Oops.Oh("查看详细失败: " + ex.Message);
}
}
#region
/// <summary>
/// 查看文档
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Readme"), HttpGet]
[DisplayName("查看文档")]
public async Task<PluginReadmeResponseModel> Readme(long id)
{
var pluginCore = await _sysPluginRep.GetByIdAsync(id);
if (pluginCore == null) throw Oops.Oh("查询插件ID失败"); ;
try
{
#region
var pluginId = pluginCore.PluginId;
var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
throw Oops.Oh($"查看详细失败: 不存在 {pluginId} 插件");
}
#endregion
PluginReadmeModel readmeModel = PluginReadmeModelFactory.Create(pluginId);
PluginReadmeResponseModel readmeResponseModel = new PluginReadmeResponseModel();
readmeResponseModel.Content = readmeModel?.Content ?? "";
readmeResponseModel.PluginId = pluginId;
return readmeResponseModel;
}
catch (Exception ex)
{
throw Oops.Oh("查看详细失败: " + ex.Message);
}
}
#endregion
/// <summary>
/// 卸载动态插件
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Uninstall"), HttpPost]
[DisplayName("卸载动态插件")]
public async Task Uninstall(DeletePluginCoreInput input)
{
var pluginCore = await _sysPluginRep.GetByIdAsync(input.Id);
if (pluginCore == null) throw Oops.Oh("查询插件ID失败");
// 卸载插件 必须 先禁用插件
#region
var pluginId = pluginCore.PluginId;
var pluginConfigModel = PluginConfigModelFactory.Create();
if (pluginConfigModel.EnabledPlugins.Contains(pluginId))
{
throw Oops.Oh("卸载失败: 请先禁用此插件");
}
string pluginDirStr = Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId);
string pluginWwwrootDirStr = Path.Combine(PluginPathProvider.PluginsWwwRootDir(), pluginId);
if (!Directory.Exists(pluginDirStr) && !Directory.Exists(pluginWwwrootDirStr))
{
throw Oops.Oh("卸载失败: 此插件不存在");
}
#endregion
try
{
// PS:卸载插件必须先禁用插件所以此时插件LoadContext已被移除释放(插件Assemblies已被释放), 此处不需移除LoadContext
// 1.删除物理文件
var pluginDir = new DirectoryInfo(pluginDirStr);
if (pluginDir.Exists)
{
pluginDir.Delete(true);
}
// 虽然 已禁用 时 pluginWwwrootDirStr/pluginId 已删除, 但为确保, 还是再删除一次
var pluginWwwrootDir = new DirectoryInfo(pluginWwwrootDirStr);
if (pluginWwwrootDir.Exists)
{
pluginWwwrootDir.Delete(true);
}
await _sysPluginRep.DeleteAsync(u => u.Id == input.Id);
}
catch (Exception ex)
{
throw Oops.Oh("卸载失败: " + ex.Message);
}
}
#region
/// <summary>
/// 插件设置设置
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Settings"), HttpGet]
[DisplayName("插件设置设置")]
public async Task<string> Settings(long id)
{
var pluginCore = await _sysPluginRep.GetByIdAsync(id);
if (pluginCore == null) throw Oops.Oh("查询插件ID失败"); ;
try
{
#region
var pluginId = pluginCore.PluginId;
var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
throw Oops.Oh($"查看详细失败: 不存在 {pluginId} 插件");
}
#endregion
string settingsJsonStr = PluginSettingsModelFactory.Create(pluginId);
return settingsJsonStr ?? "无设置项";
}
catch (Exception ex)
{
throw Oops.Oh("查看设置失败: " + ex.Message);
}
}
/// <summary>
/// 插件设置设置
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Settings"), HttpPost]
[DisplayName("插件设置设置")]
public async Task Settings(UpdatePluginCoreSettingInput input)
{
var pluginCore = await _sysPluginRep.GetByIdAsync(input.Id);
if (pluginCore == null) throw Oops.Oh("查询插件ID失败"); ;
try
{
#region
var pluginId = pluginCore.PluginId;
var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
throw Oops.Oh($"查看详细失败: 不存在 {pluginId} 插件");
}
#endregion
input.Data = input.Data ?? "";
PluginSettingsModelFactory.Save(pluginSettingsJsonStr: input.Data, pluginId: pluginCore.PluginId);
}
catch (Exception ex)
{
throw Oops.Oh("设置失败: " + ex.Message);
}
}
#endregion
/// <summary>
/// 启用插件
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Enable"), HttpPost]
[DisplayName("启用插件")]
public async Task Enable(EnablePluginCoreInput input)
{
var pluginCore = await _sysPluginRep.GetByIdAsync(input.Id);
if (pluginCore == null) return;
// 移除动态程序集/接口
var pluginConfigModel = PluginConfigModelFactory.Create();
// 效验是否存在于 已禁用插件列表
#region
var pluginId = pluginCore.PluginId;
var pluginDir = new DirectoryInfo(Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId));
if (pluginDir != null && !pluginDir.Exists)
{
throw Oops.Oh("启用失败: 此插件不存在");
}
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
if (enablePluginIds.Contains(pluginId))
{
throw Oops.Oh("启用失败: 此插件已启用");
}
#endregion
try
{
// 1. 创建插件程序集加载上下文, 添加到 PluginsLoadContexts
_pluginManager.LoadPlugin(pluginId);
// 2. 添加到 pluginConfigModel.EnabledPlugins
pluginConfigModel.EnabledPlugins.Add(pluginId);
// 4.保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
// 5. 找到此插件实例
IPlugin plugin = _pluginFinder.Plugin(pluginId);
if (plugin == null)
{
// 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json
try
{
_pluginManager.UnloadPlugin(pluginId);
}
catch (Exception ex)
{ }
// 从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
throw Oops.Oh("启用失败: 此插件不存在");
}
// 6.调取插件的 AfterEnable(), 插件开发者可在此回收资源
var pluginEnableResult = plugin.AfterEnable();
if (!pluginEnableResult.IsSuccess)
{
// 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json
try
{
_pluginManager.UnloadPlugin(pluginId);
}
catch (Exception ex)
{ }
// 从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
throw Oops.Oh("启用失败: 来自插件的错误信息: " + pluginEnableResult.Message);
}
// 7. ReBuild
this._pluginApplicationBuilderManager.ReBuild();
// 8. 尝试复制 插件下的 wwwroot 到 Plugins_wwwroot
string wwwRootDir = PluginPathProvider.WwwRootDir(pluginId);
if (Directory.Exists(wwwRootDir))
{
string targetDir = PluginPathProvider.PluginWwwRootDir(pluginId);
FileUtil.CopyFolder(wwwRootDir, targetDir);
}
//9.载入Furion动态插件
var pluginMainAssembly = _pluginManager.GetPluginAssembly(pluginId);
// 将程序集添加进动态 WebAPI 应用部件
_provider.AddAssembliesWithNotifyChanges(pluginMainAssembly);
await _sysPluginRep.UpdateAsync(u => new SysPluginCore() { Status = StatusEnum.Enable }, u => u.Id == input.Id);
}
catch (Exception ex)
{
throw Oops.Oh("启用失败: " + ex.Message);
}
}
/// <summary>
/// 禁用插件
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[ApiDescriptionSettings(Name = "Disable"), HttpPost]
[DisplayName("禁用插件")]
public async Task Disable(EnablePluginCoreInput input)
{
var pluginCore = await _sysPluginRep.GetByIdAsync(input.Id);
if (pluginCore == null) return;
// 移除动态程序集/
#region
var pluginId = pluginCore.PluginId;
var pluginConfigModel = PluginConfigModelFactory.Create();
// string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
// // 效验是否存在于 已启用插件列表
// if (!enablePluginIds.Contains(pluginId))
// {
// responseData.Code = -1;
// responseData.Message = "禁用失败: 此插件不存在, 或未启用";
// return await Task.FromResult(responseData);
// }
#endregion
try
{
// 1. 找到此插件实例
IPlugin plugin = _pluginFinder.Plugin(pluginId);
if (plugin == null)
{
throw Oops.Oh("禁用失败: 此插件不存在, 或未启用");
}
try
{
// 2.调取插件的 BeforeDisable(), 插件开发者可在此回收资源
var pluginDisableResult = plugin.BeforeDisable();
if (!pluginDisableResult.IsSuccess)
{
throw Oops.Oh("禁用失败: 来自插件的错误信息: " + pluginDisableResult.Message);
}
// 3.移除插件对应的程序集加载上下文
_pluginManager.UnloadPlugin(pluginId);
// 3.1. ReBuild
this._pluginApplicationBuilderManager.ReBuild();
if (pluginConfigModel.EnabledPlugins.Contains(pluginId))
{
// 4.从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 5.保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
}
}
catch (Exception ex)
{
LogUtil.Error<SysPluginCoreService>(ex.ToString());
throw Oops.Oh("禁用失败: 此插件不存在, 或未启用");
}
// 7. 尝试移除 Plugins_wwwroot/PluginId
string pluginWwwRootDir = PluginPathProvider.PluginWwwRootDir(pluginId);
if (Directory.Exists(pluginWwwRootDir))
{
Directory.Delete(pluginWwwRootDir, true);
}
//8.移除Furion动态插件
//9.载入Furion动态插件
var pluginMainAssembly = _pluginManager.GetPluginAssembly(pluginId);
// 将程序集添加进动态 WebAPI 应用部件
_provider.RemoveAssembliesWithNotifyChanges(pluginMainAssembly);
await _sysPluginRep.UpdateAsync(u => new SysPluginCore() { Status = StatusEnum.Disable }, u => u.Id == input.Id);
}
catch (Exception ex)
{
throw Oops.Oh("禁用失败: " + ex.Message);
}
}
/// <summary>
/// 上传文件
/// </summary>
/// <param name="file"></param>
/// <param name="path"></param>
/// <returns></returns>
[DisplayName("上传文件")]
public async Task UploadFile([Required] IFormFile file, [FromQuery] string? path)
{
var sysFile = await HandleUploadFile(file, path);
//return new FileOutput
//{
//};
}
/// <summary>
/// 上传文件
/// </summary>
/// <param name="file">文件</param>
/// <param name="savePath">路径</param>
/// <returns></returns>
[NonAction]
private async Task<BaseResponseModel> HandleUploadFile(IFormFile file, string savePath)
{
if (file == null) throw Oops.Oh(ErrorCodeEnum.D8000);
var path = savePath;
BaseResponseModel responseData = new BaseResponseModel();
#region
if (file == null)
{
throw Oops.Oh("上传的文件不能为空");
//responseData.Code = -1;
//responseData.Message = "上传的文件不能为空";
//return responseData;
}
//文件后缀
string fileExtension = Path.GetExtension(file.FileName);//获取文件格式,拓展名
// 类型标记
UploadFileType uploadFileType = UploadFileType.NoAllowedType;
switch (fileExtension)
{
case ".zip":
uploadFileType = UploadFileType.Zip;
break;
case ".nupkg":
uploadFileType = UploadFileType.Nupkg;
break;
}
if (fileExtension != ".zip" && fileExtension != ".nupkg")
{
throw Oops.Oh("只能上传 zip 或 nupkg 格式文件");
//responseData.Code = -1;
//// nupkg 其实就是 zip
//responseData.Message = "只能上传 zip 或 nupkg 格式文件";
//return responseData;
}
// PluginCore.AspNetCore-v1.0.2 起 不再限制插件上传大小
//判断文件大小
//var fileSize = file.Length;
//if (fileSize > 1024 * 1024 * 5) // 5M
//{
// responseData.Code = -1;
// responseData.Message = "上传的文件不能大于5MB";
// return responseData;
//}
#endregion
try
{
// 1.先上传到 临时插件上传目录, 用Guid.zip作为保存文件名
string tempZipFilePath = Path.Combine(PluginPathProvider.TempPluginUploadDir(), Guid.NewGuid() + ".zip");
using (var fs = System.IO.File.Create(tempZipFilePath))
{
file.CopyTo(fs); //将上传的文件文件流复制到fs中
fs.Flush();//清空文件流
}
// 2.解压
bool isDecomparessSuccess = false;
if (uploadFileType == UploadFileType.Zip)
{
isDecomparessSuccess = ZipHelper.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", ""));
}
else if (uploadFileType == UploadFileType.Nupkg)
{
isDecomparessSuccess = NupkgService.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", ""));
}
// 3.删除原压缩包
System.IO.File.Delete(tempZipFilePath);
if (!isDecomparessSuccess)
{
throw Oops.Oh("解压插件压缩包失败");
//responseData.Code = -1;
//responseData.Message = "解压插件压缩包失败";
//return responseData;
}
// 4.读取其中的info.json, 获取 PluginId 值
PluginInfoModel pluginInfoModel = PluginInfoModelFactory.ReadPluginDir(tempZipFilePath.Replace(".zip", ""));
if (pluginInfoModel == null || string.IsNullOrEmpty(pluginInfoModel.PluginId))
{
// 记得删除已不再需要的临时插件文件夹
Directory.Delete(tempZipFilePath.Replace(".zip", ""), true);
throw Oops.Oh("不合法的插件");
//responseData.Code = -1;
//responseData.Message = "不合法的插件";
//return responseData;
}
string pluginId = pluginInfoModel.PluginId;
// 5.检索 此 PluginId 是否本地插件已存在
var pluginConfigModel = PluginConfigModelFactory.Create();
// 本地已经存在的 PluginId
IList<string> localExistPluginIds = PluginPathProvider.AllPluginFolderName();
if (localExistPluginIds.Contains(pluginId))
{
// 记得删除已不再需要的临时插件文件夹
Directory.Delete(tempZipFilePath.Replace(".zip", ""), true);
throw Oops.Oh($"本地已有此插件 (PluginId: {pluginId}), 请前往插件列表删除后, 再上传");
//responseData.Code = -1;
//responseData.Message = $"本地已有此插件 (PluginId: {pluginId}), 请前往插件列表删除后, 再上传";
//return responseData;
}
// 6.本地无此插件 -> 移动插件文件夹到 Plugins 下, 并以 PluginId 为插件文件夹名
string pluginsRootPath = PluginPathProvider.PluginsRootPath();
string newPluginDir = Path.Combine(pluginsRootPath, pluginId);
Directory.Move(tempZipFilePath.Replace(".zip", ""), newPluginDir);
// 7. 放入 Plugins 中, 默认为 已禁用
var pluginCore = new SysPluginCore();
pluginCore.PluginId = pluginId;
pluginCore.DisplayName = pluginInfoModel.DisplayName;
pluginCore.Description = pluginInfoModel.Description;
pluginCore.Author = pluginInfoModel.Author;
pluginCore.Version = pluginInfoModel.Version;
pluginCore.Status = StatusEnum.Disable;
await _sysPluginRep.InsertAsync(pluginCore.Adapt<SysPluginCore>());
responseData.Code = 1;
responseData.Message = $"上传插件成功 (PluginId: {pluginId})";
}
catch (Exception ex)
{
throw Oops.Oh("上传插件失败: " + ex.Message);
//responseData.Code = -1;
//responseData.Message = "上传插件失败: " + ex.Message;
//ex = ex.InnerException;
//while (ex != null)
//{
// responseData.Message += " - " + ex.InnerException.Message;
// ex = ex.InnerException;
//}
}
return responseData;
}
#region Helpers
[NonAction]
private IList<PluginInfoResponseModel> PluginInfoModelToResponseModel(IList<PluginInfoModel> pluginInfoModels, PluginConfigModel pluginConfigModel, string[] enablePluginIds)
{
// 获取 Plugins 下所有插件
// DirectoryInfo pluginsDir = new DirectoryInfo(PluginPathProvider.PluginsRootPath());
// List<string> pluginIds = pluginsDir?.GetDirectories()?.Select(m => m.Name)?.ToList() ?? new List<string>();
IList<PluginInfoResponseModel> responseModels = new List<PluginInfoResponseModel>();
#region
foreach (var model in pluginInfoModels)
{
PluginInfoResponseModel responseModel = new PluginInfoResponseModel();
responseModel.Author = model.Author;
responseModel.Description = model.Description;
responseModel.DisplayName = model.DisplayName;
responseModel.PluginId = model.PluginId;
responseModel.SupportedVersions = model.SupportedVersions;
responseModel.Version = model.Version;
responseModel.DependPlugins = model.DependPlugins;
if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && !enablePluginIds.Contains(model.PluginId))
{
// 错误情况: 配置 标识 已启用, 但实际没有启用成功
pluginConfigModel.EnabledPlugins.Remove(model.PluginId);
PluginConfigModelFactory.Save(pluginConfigModel);
responseModel.Status = PluginStatus.Disabled;
}
else if (!pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId))
{
// 错误情况: 配置没有标识 已启用, 但实际 已启用
pluginConfigModel.EnabledPlugins.Add(model.PluginId);
PluginConfigModelFactory.Save(pluginConfigModel);
responseModel.Status = PluginStatus.Enabled;
}
else if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId))
{
responseModel.Status = PluginStatus.Enabled;
}
else
{
responseModel.Status = PluginStatus.Disabled;
}
responseModels.Add(responseModel);
}
#endregion
return responseModels;
}
public enum UploadFileType
{
NoAllowedType = 0,
Zip = 1,
Nupkg = 2
}
#endregion Helpers
}

View File

@ -1,25 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
namespace Admin.NET.Plugin.PluginCore;
[AppStartup(100)]
public class Startup : AppStartup
{
public void ConfigureServices(IServiceCollection services)
{
RunOptions.Default
.AddComponent<StartupServiceComponent>()
.UseComponent<StartupApplicationComponent>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
}
}

View File

@ -1,57 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using PluginCore;
using PluginCore.AspNetCore.Extensions;
using PluginCore.Interfaces;
using PluginCore.Models;
namespace Admin.NET.Plugin.PluginCore;
// 模拟 ConfigureService
public sealed class StartupServiceComponent : IServiceComponent
{
public void Load(IServiceCollection services, ComponentContext componentContext)
{
// 1. 添加 PluginCore
services.AddPluginCore();
IPluginManager pluginManager = App.GetService<IPluginManager>();
IDynamicApiRuntimeChangeProvider provider = App.GetService<IDynamicApiRuntimeChangeProvider>();
#region PluginConfigModel
PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create();
#endregion PluginConfigModel
// 已启用的插件
#region Assemblies
IList<string> enabledPluginIds = pluginConfigModel.EnabledPlugins;
foreach (var pluginId in enabledPluginIds)
{
//9.载入Furion动态插件
var pluginMainAssembly = pluginManager.GetPluginAssembly(pluginId);
// 将程序集添加进动态 WebAPI 应用部件
provider.AddAssembliesWithNotifyChanges(pluginMainAssembly);
}
#endregion Assemblies
}
}
// 模拟 Configure
public sealed class StartupApplicationComponent : IApplicationComponent
{
public void Load(IApplicationBuilder app, IWebHostEnvironment env, ComponentContext componentContext)
{
// 2. 使用 PluginCore
app.UsePluginCore();
}
}

View File

@ -1,94 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
namespace PluginCore.AspNetCore.AdminUI
{
public static class PluginCoreAdminUIBuilderExtensions
{
/// <summary>
/// Register the SwaggerUI middleware with provided options
/// </summary>
public static IApplicationBuilder UsePluginCoreAdminUI(this IApplicationBuilder app, PluginCoreAdminUIOptions options)
{
#region Old -
//Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create();
//switch (pluginCoreConfig.FrontendMode?.ToLower())
//{
// case "localembedded":
// app.UseMiddleware<PluginCoreAdminUIMiddleware>(options);
// break;
// case "localfolder":
// #region LocalFolder
// //string contentRootPath = Directory.GetCurrentDirectory();
// // https://docs.microsoft.com/zh-CN/aspnet/core/fundamentals/static-files?view=aspnetcore-5.0
// //var options = new DefaultFilesOptions()
// //{
// // RequestPath = "/PluginCore/Admin",
// //};
// //// TODO: 404: 无效, 失败, 改为使用 Controller 手动指定
// ////options.DefaultFileNames.Add("PluginCoreAdmin/index.html");
// //app.UseDefaultFiles(options);
// // 注意: 为了无需重启Web而更新是否本地前端配置, 因此此项保持常驻开启
// // 因此, 需要保证 PluginCoreAdmin 文件夹存在
// string pluginCoreAdminDir = PluginPathProvider.PluginCoreAdminDir();
// app.UseStaticFiles(new StaticFileOptions
// {
// FileProvider = new PhysicalFileProvider(
// pluginCoreAdminDir),
// RequestPath = "/PluginCore/Admin"
// });
// #endregion
// break;
// case "remotecdn":
// break;
// default:
// app.UseMiddleware<PluginCoreAdminUIMiddleware>(options);
// break;
//}
//return app;
#endregion
return app.UseMiddleware<PluginCoreAdminUIMiddleware>(options);
}
/// <summary>
/// Register the SwaggerUI middleware with optional setup action for DI-injected options
/// </summary>
public static IApplicationBuilder UsePluginCoreAdminUI(
this IApplicationBuilder app)
{
PluginCoreAdminUIOptions options = new PluginCoreAdminUIOptions()
{
};
return app.UsePluginCoreAdminUI(options);
}
}
}

View File

@ -1,149 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace PluginCore.AspNetCore.AdminUI
{
public class PluginCoreAdminUIMiddleware
{
private const string EmbeddedFileNamespace = "PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist";
private readonly PluginCoreAdminUIOptions _options;
private readonly StaticFileMiddleware _staticFileMiddleware;
public PluginCoreAdminUIMiddleware(
RequestDelegate next,
IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory,
PluginCoreAdminUIOptions options)
{
_options = options ?? new PluginCoreAdminUIOptions();
_staticFileMiddleware = CreateStaticFileMiddleware(next, hostingEnv, loggerFactory, options);
}
public async Task Invoke(HttpContext httpContext)
{
var httpMethod = httpContext.Request.Method;
var path = httpContext.Request.Path.Value;
// If the RoutePrefix is requested (with or without trailing slash), redirect to index URL
if (httpMethod == "GET" && Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase))
{
// Use relative redirect to support proxy environments
var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/")
? "index.html"
: $"{path.Split('/').Last()}/index.html";
RespondWithRedirect(httpContext.Response, relativeIndexUrl);
return;
}
if (httpMethod == "GET" && Regex.IsMatch(path, $"^/{Regex.Escape(_options.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase))
{
await RespondWithIndexHtml(httpContext.Response);
return;
}
await _staticFileMiddleware.Invoke(httpContext);
}
private StaticFileMiddleware CreateStaticFileMiddleware(
RequestDelegate next,
IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory,
PluginCoreAdminUIOptions options)
{
Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create();
IFileProvider fileProvider = null;
switch (pluginCoreConfig.FrontendMode?.ToLower())
{
case "localembedded":
fileProvider = new EmbeddedFileProvider(typeof(PluginCoreAdminUIMiddleware).GetTypeInfo().Assembly,
EmbeddedFileNamespace);
break;
case "localfolder":
string absoluteRootPath = PluginPathProvider.PluginCoreAdminDir();
fileProvider = new PhysicalFileProvider(absoluteRootPath);
break;
case "remotecdn":
fileProvider = new PluginCoreAdminUIRemoteFileProvider(pluginCoreConfig.RemoteFrontend);
break;
default:
fileProvider = new EmbeddedFileProvider(typeof(PluginCoreAdminUIMiddleware).GetTypeInfo().Assembly,
EmbeddedFileNamespace);
break;
}
var staticFileOptions = new StaticFileOptions
{
RequestPath = string.IsNullOrEmpty(options.RoutePrefix) ? string.Empty : $"/{options.RoutePrefix}",
FileProvider = fileProvider,
};
return new StaticFileMiddleware(next, hostingEnv, Options.Create(staticFileOptions), loggerFactory);
}
private void RespondWithRedirect(HttpResponse response, string location)
{
response.StatusCode = 301;
response.Headers["Location"] = location;
}
private async Task RespondWithIndexHtml(HttpResponse response)
{
response.StatusCode = 200;
response.ContentType = "text/html;charset=utf-8";
using (var stream = _options.IndexStream())
{
// Inject arguments before writing to response
var htmlBuilder = new StringBuilder(new StreamReader(stream).ReadToEnd());
foreach (var entry in GetIndexArguments())
{
htmlBuilder.Replace(entry.Key, entry.Value);
}
await response.WriteAsync(htmlBuilder.ToString(), Encoding.UTF8);
}
}
private IDictionary<string, string> GetIndexArguments()
{
return new Dictionary<string, string>()
{
//{ "%(DocumentTitle)", _options.DocumentTitle },
//{ "%(HeadContent)", _options.HeadContent },
//{ "%(ConfigObject)", JsonSerializer.Serialize(_options.ConfigObject, _jsonSerializerOptions) },
//{ "%(OAuthConfigObject)", JsonSerializer.Serialize(_options.OAuthConfigObject, _jsonSerializerOptions) },
//{ "%(Interceptors)", JsonSerializer.Serialize(_options.Interceptors) },
};
}
}
}

View File

@ -1,67 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Text;
namespace PluginCore.AspNetCore.AdminUI
{
public class PluginCoreAdminUIOptions
{
/// <summary>
/// Gets or sets a route prefix for accessing the swagger-ui
/// </summary>
public string RoutePrefix { get; set; } = "PluginCore/Admin";
/// <summary>
/// Gets or sets a Stream function for retrieving the swagger-ui page
/// </summary>
public Func<Stream> IndexStream
{
get
{
Func<Stream> funcStream = null;
;
Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create();
switch (pluginCoreConfig.FrontendMode?.ToLower())
{
case "localembedded":
funcStream = () => typeof(PluginCoreAdminUIOptions).GetTypeInfo().Assembly
.GetManifestResourceStream("PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist.index.html");
break;
case "localfolder":
string absoluteRootPath = PluginPathProvider.PluginCoreAdminDir();
string indexFilePath = Path.Combine(absoluteRootPath, "index.html");
funcStream = () => (Stream)new FileStream(indexFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1, FileOptions.Asynchronous | FileOptions.SequentialScan);
break;
case "remotecdn":
string remoteFrontendRootPath = pluginCoreConfig.RemoteFrontend;
string indexFileRemotePath = remoteFrontendRootPath + "/" + "index.html";
funcStream = () => new HttpClient().GetStreamAsync(indexFileRemotePath).Result;
break;
default:
funcStream = () => typeof(PluginCoreAdminUIOptions).GetTypeInfo().Assembly
.GetManifestResourceStream("PluginCore.AspNetCore.node_modules.plugincore_admin_frontend.dist.index.html");
break;
}
return funcStream;
}
}
}
}

View File

@ -1,131 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Primitives;
namespace PluginCore.AspNetCore.AdminUI
{
public class PluginCoreAdminUIRemoteFileProvider : IFileProvider
{
protected string RootUrl { get; set; }
public PluginCoreAdminUIRemoteFileProvider(string rootUrl)
{
this.RootUrl = rootUrl;
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
return (IDirectoryContents)NotFoundDirectoryContents.Singleton;
}
public IFileInfo GetFileInfo(string subpath)
{
if (string.IsNullOrEmpty(subpath))
return (IFileInfo)new NotFoundFileInfo(subpath);
IFileInfo fileInfo = new PluginCoreAdminUIFileInfo(this.RootUrl, subpath);
return fileInfo;
}
public IChangeToken Watch(string filter)
{
throw new NotImplementedException();
}
public class PluginCoreAdminUIFileInfo : IFileInfo
{
protected string RootUrl { get; set; }
protected string SubPath { get; set; }
private string _name;
public PluginCoreAdminUIFileInfo(string rootUrl, string subpath)
{
this.RootUrl = rootUrl;
this.SubPath = subpath;
this._name = this.SubPath.Substring(this.SubPath.LastIndexOf("/") + 1);
}
public Stream CreateReadStream()
{
HttpClient httpClient = new HttpClient();
return httpClient.GetStreamAsync($"{this.RootUrl}/{this.SubPath}").Result;
}
public bool Exists
{
get
{
bool isExist = false;
if (this.Name == "index.html")
{
isExist = true;
}
return isExist;
}
}
public bool IsDirectory
{
get
{
return false;
}
}
public DateTimeOffset LastModified
{
get
{
return new DateTimeOffset(DateTime.Now);
}
}
public long Length
{
get
{
return 111;
}
}
public string Name
{
get
{
return this._name;
}
}
public string PhysicalPath
{
get
{
return "";
}
}
}
}
}

View File

@ -1,71 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PluginCore.AspNetCore.Authorization;
namespace PluginCore.AspNetCore.Authentication
{
/// <summary>
/// https://stackoverflow.com/questions/52287542/invalidoperationexception-no-authenticationscheme-was-specified-and-there-was
/// </summary>
public class PluginCoreAuthenticationHandler : AuthenticationHandler<PluginCoreAuthenticationSchemeOptions>
{
private readonly AccountManager _accountManager;
public PluginCoreAuthenticationHandler(IOptionsMonitor<PluginCoreAuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, AccountManager accountManager) : base(options, logger, encoder, clock)
{
this._accountManager = accountManager;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = this._accountManager.CurrentToken();
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
bool isAdmin = AccountManager.IsAdminToken(token);
if (!isAdmin)
{
return AuthenticateResult.Fail($"token is not admin");
}
else
{
var id = new ClaimsIdentity(
// new Claim[] { new Claim("PluginCore.Token", token) }, // not safe , just as an example , should custom claims on your own
claims: new Claim[] { new Claim(type: IPlugins.Constants.AspNetCoreAuthenticationClaimType, value: token) }, // not safe , just as an example , should custom claims on your own
authenticationType: Scheme.Name
);
ClaimsPrincipal principal = new ClaimsPrincipal(identity: id);
var ticket = new AuthenticationTicket(
principal: principal,
properties: new AuthenticationProperties(),
authenticationScheme: Scheme.Name);
// Utils.LogUtil.Info<PluginCoreAuthenticationHandler>($"通过 Authentication: token: {token}");
Utils.LogUtil.Info<PluginCoreAuthenticationHandler>($"Authentication Passed");
return AuthenticateResult.Success(ticket);
}
}
}
}

View File

@ -1,22 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Authentication;
namespace PluginCore.AspNetCore.Authentication
{
public class PluginCoreAuthenticationSchemeOptions : AuthenticationSchemeOptions
{
}
}

View File

@ -1,148 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Http;
using PluginCore.Utils;
namespace PluginCore.AspNetCore.Authorization
{
public class AccountManager
{
private readonly IHttpContextAccessor _httpContextAccessor;
public Microsoft.AspNetCore.Http.HttpContext HttpContext
{
get
{
return this._httpContextAccessor.HttpContext;
}
}
public AccountManager(IHttpContextAccessor httpContextAccessor)
{
// Exception: IFeatureCollection has been disposed. Object name: 'Collection'.
// https://stackoverflow.com/questions/59963383/session-setstring-method-throws-exception-ifeaturecollection-has-been-disposed
//HttpContext = ((HttpContextAccessor)httpContextAccessor).HttpContext;
//HttpContext = httpContextAccessor.HttpContext;
// 注意: 不要将 HttpContext 保存起来,应当每次都从 httpContextAccessor 取
_httpContextAccessor = httpContextAccessor;
}
public static Config.PluginCoreConfig.AdminModel Admin
{
get
{
return Config.PluginCoreConfigFactory.Create().Admin;
}
set
{
var sourceModel = Config.PluginCoreConfigFactory.Create();
sourceModel.Admin = value;
Config.PluginCoreConfigFactory.Save(sourceModel);
}
}
public static string CurrentToken(HttpContext httpContext)
{
string token = null;
HttpRequest request = httpContext.Request;
try
{
// header -> cookie
try
{
// header 中找 token
if (request.Headers.ContainsKey("Authorization"))
{
string authHeader = request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer"))
{
token = authHeader.Substring("Bearer ".Length).Trim();
}
}
}
catch (Exception ex)
{
//throw ex;
}
if (string.IsNullOrEmpty(token))
{
// cookie 中找 token
//string tokenCookieName = "token";
// string tokenCookieName = "PluginCore.Admin.Token";
string tokenCookieName = IPlugins.Constants.AspNetCoreAuthorizationTokenCookieName;
if (request.Cookies.Keys.Contains(tokenCookieName))
{
if (request.Cookies[tokenCookieName] != null && string.IsNullOrEmpty(request.Cookies[tokenCookieName]) == false)
{
token = request.Cookies[tokenCookieName];
}
}
}
}
catch (Exception ex)
{
throw ex;
}
return token;
}
public string CurrentToken()
{
return CurrentToken(this.HttpContext);
}
public static string CreateToken()
{
return CreateToken(Admin.UserName, Admin.Password);
}
public static string CreateToken(string userName, string password)
{
string token = $"UserName={userName}&Password={password}";
token = Md5Helper.MD5Encrypt32(token);
return token;
}
public static bool IsAdminToken(string token)
{
bool isAdmin = false;
isAdmin = CreateToken().Equals(token);
return isAdmin;
}
public bool IsAdmin()
{
return IsAdmin(this.HttpContext);
}
public static bool IsAdmin(HttpContext httpContext)
{
bool isAdmin = false;
try
{
string currentToken = CurrentToken(httpContext);
isAdmin = IsAdminToken(currentToken);
}
catch (Exception ex)
{
throw ex;
}
return isAdmin;
}
}
}

View File

@ -1,58 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
namespace PluginCore.AspNetCore.Authorization
{
public class PluginCoreAdminAuthorizationHandler : AuthorizationHandler<PluginCoreAdminRequirement>
{
private readonly AccountManager _accountManager;
public PluginCoreAdminAuthorizationHandler(AccountManager accountManager)
{
_accountManager = accountManager;
}
/// <summary>
/// 必须在其中呼叫一次 <see cref="AuthorizationHandlerContext.Succeed(IAuthorizationRequirement)"/> 代表满足 <see cref="PluginCoreAdminRequirement"/>,否则皆为 不满足此 Requirement
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
PluginCoreAdminRequirement requirement)
{
bool isAdmin = this._accountManager.IsAdmin();
if (!isAdmin)
{
context.Fail();
}
else
{
// 认证通过后, 可通过下面方式获取 token
var identity = context.User.Identity;
string token = this._accountManager.CurrentToken();
// Utils.LogUtil.Info<PluginCoreAdminAuthorizationHandler>($"通过 Authorization: token: {token}");
Utils.LogUtil.Info<PluginCoreAdminAuthorizationHandler>($"Authorization Granted");
context.Succeed(requirement);
}
await Task.CompletedTask;
}
}
}

View File

@ -1,30 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Authorization;
namespace PluginCore.AspNetCore.Authorization
{
/// <summary>
/// 注意: PluginCoreAdmin -> PluginCore.Admin
/// </summary>
public class PluginCoreAdminAuthorizeAttribute : AuthorizeAttribute
{
// public PluginCoreAdminAuthorizeAttribute() : base("PluginCore.Admin")
public PluginCoreAdminAuthorizeAttribute() : base(policy: IPlugins.Constants.AspNetCoreAuthorizationPolicyName)
{
// 同时明确指定 认证方案 与 授权策略
AuthenticationSchemes = PluginCore.IPlugins.Constants.AspNetCoreAuthenticationScheme;
}
}
}

View File

@ -1,25 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Authorization;
namespace PluginCore.AspNetCore.Authorization
{
public class PluginCoreAdminRequirement : IAuthorizationRequirement
{
public PluginCoreAdminRequirement()
{
}
}
}

View File

@ -1,26 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.Extensions.DependencyInjection;
namespace PluginCore.AspNetCore.BackgroundServices
{
public static class BackgroundServicesHelper
{
public static IServiceCollection AddBackgroundServices(this IServiceCollection services)
{
//services.AddScoped(typeof(IHostedService), typeof(TimeBackgroundService));
// AddHostedService: Microsoft.AspNetCore.App
services.AddHostedService<PluginTimeJobBackgroundService>(); // 以这种方式注入就是单例
return services;
}
}
}

View File

@ -1,91 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using PluginCore.Interfaces;
using PluginCore.IPlugins;
using PluginCore.Utils;
namespace PluginCore.AspNetCore.BackgroundServices
{
public class PluginTimeJobBackgroundService : TimeBackgroundService
{
/// <summary>
/// 插件与之最近执行时间
/// <para>最近执行时间: 10位秒 时间戳</para>
/// </summary>
private readonly Dictionary<string, long> _pluginAndLastExecuteTimeDic = new Dictionary<string, long>();
private readonly IPluginFinder _pluginFinder;
private static readonly object _doWorklock = new object();
public PluginTimeJobBackgroundService(IPluginFinder pluginFinder)
{
_pluginFinder = pluginFinder;
// 最小间隔 1 秒
_timerPeriod = TimeSpan.FromSeconds(1);
}
protected override void DoWork(object state)
{
lock (_doWorklock)
{
//Console.WriteLine("Memory used before collection: {0:N0}", GC.GetTotalMemory(false));
var plugins = this._pluginFinder.EnablePlugins<ITimeJobPlugin>().ToList();
List<string> enabledPluginKeyList = new List<string>();
foreach (var item in plugins)
{
string pluginKey = item.GetType().ToString();
enabledPluginKeyList.Add(pluginKey);
if (this._pluginAndLastExecuteTimeDic.ContainsKey(pluginKey))
{
long lastExecuteTime = this._pluginAndLastExecuteTimeDic[pluginKey];
long nowTime = DateTime.Now.ToTimeStamp10();
if (nowTime - lastExecuteTime >= item.SecondsPeriod)
{
// 调用
Utils.LogUtil.Info<PluginTimeJobBackgroundService>($"{pluginKey}: {nameof(ITimeJobPlugin)}.{nameof(ITimeJobPlugin.ExecuteAsync)}");
Task task = item?.ExecuteAsync();
this._pluginAndLastExecuteTimeDic[pluginKey] = DateTime.Now.ToTimeStamp10();
}
}
else
{
// 调用
Utils.LogUtil.Info<PluginTimeJobBackgroundService>($"{pluginKey}: {nameof(ITimeJobPlugin)}.{nameof(ITimeJobPlugin.ExecuteAsync)}");
Task task = item?.ExecuteAsync();
this._pluginAndLastExecuteTimeDic.Add(pluginKey, DateTime.Now.ToTimeStamp10());
}
}
// 所有插件遍历结束
// 出现在了 _pluginAndLastExecuteTimeDic 中,但没有出现在 enabledPluginKeyList, 说明为之前启用过,但现在已禁用的插件,需要去除掉
List<string> keys = this._pluginAndLastExecuteTimeDic.Select(m => m.Key).ToList();
foreach (string key in keys)
{
if (!enabledPluginKeyList.Contains(key))
{
this._pluginAndLastExecuteTimeDic.Remove(key);
}
}
GC.Collect();
GC.WaitForPendingFinalizers();
//Console.WriteLine("Memory used after full collection: {0:N0}", GC.GetTotalMemory(true));
}
}
}
}

View File

@ -1,46 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace PluginCore.AspNetCore.BackgroundServices
{
public abstract class TimeBackgroundService : IHostedService, IDisposable
{
protected Timer _timer;
protected TimeSpan _timerPeriod;
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(DoWork, null, TimeSpan.Zero, _timerPeriod);
return Task.CompletedTask;
}
public virtual Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
protected abstract void DoWork(object state);
public virtual void Dispose()
{
_timer?.Dispose();
}
}
}

View File

@ -1,280 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
---
## [unreleased]
### Documentation
- **(CHANGELOG.md)** update - ([85bf0d9](https://github.com/yiyungent/PluginCore/commit/85bf0d9bd8e2f9efa7e8c0db3127ec8aaa054bc8)) - github-actions[bot]
- **(CHANGELOG.md)** update - ([a329e4e](https://github.com/yiyungent/PluginCore/commit/a329e4e7ec4048ec445e826f4536402abd832e46)) - github-actions[bot]
- **(CHANGELOG.md)** update - ([0bbfc89](https://github.com/yiyungent/PluginCore/commit/0bbfc8955b7f6338db2125c78ec250e9eeeadcce)) - github-actions[bot]
- **(CHANGELOG.md)** update - ([4f6b47b](https://github.com/yiyungent/PluginCore/commit/4f6b47b3f86bfce4a8f660166837a7322c568d78)) - github-actions[bot]
### Miscellaneous Chores
- **(src/plugincore.aspnetcore)** cliff.toml, CHANGELOG.md - ([bf95c28](https://github.com/yiyungent/PluginCore/commit/bf95c287ae9902608c4711e4dec05575c2d0e794)) - yiyun
---
## [PluginCore.AspNetCore-v1.4.3](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.4.2..PluginCore.AspNetCore-v1.4.3) - 2024-08-31
### Bug Fixes
- **(PluginCore.AspNetCore/Controllers/PluginsController)** swagger [FromForm] - ([241d9a7](https://github.com/yiyungent/PluginCore/commit/241d9a72973d9cf1a11c11264a91e9370b2a6cda)) - yiyun
### Features
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** 插件 wwwroot 默认页 指定, 无需再手动 - ([469ae81](https://github.com/yiyungent/PluginCore/commit/469ae81fc31adce0c0b0701e2dfa1d98634ee184)) - yiyun
### Miscellaneous Chores
- **(PluginCore.AspNetCore.csproj)** 1.4.2 -> 1.4.3 - ([1924e0c](https://github.com/yiyungent/PluginCore/commit/1924e0c179aaac1a1d1f57a8bdebc7578d0b7650)) - yiyun
---
## [PluginCore.AspNetCore-v1.4.2](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.4.1..PluginCore.AspNetCore-v1.4.2) - 2024-04-06
### Features
- **(src/plugincore.aspnetcore/backgroundservices/plugintimejobbackgroundservice.cs)** log - ([5da04c2](https://github.com/yiyungent/PluginCore/commit/5da04c20b57a166811ef8af828c5fd0bafab844c)) - yiyun
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** plugin:startup->appstart - ([71cafe7](https://github.com/yiyungent/PluginCore/commit/71cafe7fa7348b4d8ccb0b342f31ff848c6e77e9)) - yiyun
### Build
- **(src/plugincore.aspnetcore/plugincore.aspnetcore.csproj)** 1.4.1 -> 1.4.2 - ([75f17be](https://github.com/yiyungent/PluginCore/commit/75f17becb01b6d833759811e65cf464ea7a745b1)) - yiyun
---
## [PluginCore.AspNetCore-v1.4.1](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.4.0..PluginCore.AspNetCore-v1.4.1) - 2024-03-14
### Bug Fixes
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** log, _serviceProvider - ([7d7a904](https://github.com/yiyungent/PluginCore/commit/7d7a904fd1794e26f278655f6f3f286b92bd1492)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** 1.4.0 -> 1.4.1 - ([393d861](https://github.com/yiyungent/PluginCore/commit/393d861870c45a70f63421b5451df76e4ed9c808)) - yiyun
---
## [PluginCore.AspNetCore-v1.4.0](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.3.4..PluginCore.AspNetCore-v1.4.0) - 2024-02-15
### Features
- **(src/plugincore.aspnetcore)** authentication & Authorize - ([73a673e](https://github.com/yiyungent/PluginCore/commit/73a673e06d2a8d662a44323cf7401e91d814ae90)) - yiyun
- **(src/plugincore.aspnetcore)** 认证与授权: 优化,分离, PluginCoreStartupExtensions 优化 - ([ebab6d8](https://github.com/yiyungent/PluginCore/commit/ebab6d888a303f221e4a45ddaa28107ff4a012c4)) - yiyun
- **(src/plugincore.aspnetcore)** accountManager 部分方法静态化, 提供 HttpContext 传入方式, 相关引用处更新调用 - ([491f334](https://github.com/yiyungent/PluginCore/commit/491f334baabb28e1736b5cc1bd19a0081c54949c)) - yiyun
### Miscellaneous Chores
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** 代码缩进: 美化 - ([6a03628](https://github.com/yiyungent/PluginCore/commit/6a036289ba6f3ae1592b4e4ed58b4fb1bcdc1306)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** <Version>1.3.4</Version> -> <Version>1.4.0</Version> - ([f786f5e](https://github.com/yiyungent/PluginCore/commit/f786f5eeb5e19558e3e73890a42f56ce812784cc)) - yiyun
---
## [PluginCore.AspNetCore-v1.3.4](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.3.3..PluginCore.AspNetCore-v1.3.4) - 2023-12-30
### Bug Fixes
- **(src/plugincore.aspnetcore)** 转向/适配 LogUtil - ([1a7c71d](https://github.com/yiyungent/PluginCore/commit/1a7c71d748fd228f1053de67572a04f466012ab8)) - yiyun
- **(src/plugincore.aspnetcore)** 适配: LogUtil - ([f150d07](https://github.com/yiyungent/PluginCore/commit/f150d07b55326133afdfe2c156464605483feb05)) - yiyun
- **(src/plugincore.aspnetcore)** 适配 LogUtil.Error - ([9c82b16](https://github.com/yiyungent/PluginCore/commit/9c82b161c5507ad1a7b78b1191dba208e8f4be45)) - yiyun
- **(src/plugincore.aspnetcore/middlewares/languagemiddleware.cs)** namespace: 语法降级 - ([a1f83fc](https://github.com/yiyungent/PluginCore/commit/a1f83fce8f1dcf2e5ead3b8a15eae006053af4de)) - yiyun
### Documentation
- **(src/plugincore.aspnetcore/readme.txt)** zh -> EN - ([f822b0b](https://github.com/yiyungent/PluginCore/commit/f822b0b79d1afca536bb7e10814226fb65988365)) - yiyun
- **(src/plugincore.aspnetcore/readme.txt)** update - ([0a3210b](https://github.com/yiyungent/PluginCore/commit/0a3210bc073252a1f18d0910dc80aabd1061a66f)) - yiyun
### Features
- **(src/**/*.cs)** // License: Apache-2.0 -> // License: GNU LGPLv3 - ([57366d3](https://github.com/yiyungent/PluginCore/commit/57366d3e2afdb8e20e94851aa8a09f1ee61b6d7e)) - yiyun
- **(src/**/*.cs)** // Project: https://moeci.com/PluginCore -> // Project: https://yiyungent.github.io/PluginCore - ([7420480](https://github.com/yiyungent/PluginCore/commit/742048065978c1b8597fab3d52f011db4247fbda)) - yiyun
- **(src/plugincore.aspnetcore)** use Constants - ([6cd128a](https://github.com/yiyungent/PluginCore/commit/6cd128a2ce6da83f8cfee46ae03a7af44380e791)) - yiyun
- **(src/plugincore.aspnetcore)** languageMiddleware: 当前 Language - ([b0d79e7](https://github.com/yiyungent/PluginCore/commit/b0d79e7a0ae469cc295d87ed8a7c97a355cbc7a1)) - yiyun
- **(src/plugincore.aspnetcore)** 适配: LogUtil - ([b09da39](https://github.com/yiyungent/PluginCore/commit/b09da39199d614f40ec533d7a472069c40121fae)) - yiyun
- **(src/plugincore.aspnetcore)** 认证与授权: 日志输出: 中文->英文 - ([1470a99](https://github.com/yiyungent/PluginCore/commit/1470a9913fff6e0df37495d1143f5a55eed995fd)) - yiyun
- **(src/plugincore.aspnetcore/controllers/pluginscontroller.cs)** 启用,禁用: Message: 使用 BasePlugin 源 - ([d6c8a33](https://github.com/yiyungent/PluginCore/commit/d6c8a3361a7573c68031ad6e8bd8aa35c65035d4)) - yiyun
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** use Constants - ([68952a1](https://github.com/yiyungent/PluginCore/commit/68952a13613d469cefe9fc9fd13ab7525dd93f78)) - yiyun
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** use constants - ([37798ef](https://github.com/yiyungent/PluginCore/commit/37798efc92f82e5e5f4d696b6de5466809a18d48)) - yiyun
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** 初始化: Logger - ([6e24a5a](https://github.com/yiyungent/PluginCore/commit/6e24a5a391af60f8ec0ba85e8ea86ea23c49f522)) - yiyun
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** log 输出 -> 英文 - ([e41b73a](https://github.com/yiyungent/PluginCore/commit/e41b73adda0ca5d9f1f60109ee8b6fe7de39deda)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** 1.3.3 -> 1.3.4 - ([84550cc](https://github.com/yiyungent/PluginCore/commit/84550cc9159c387cedbc8beda282dfcfea9cdcb1)) - yiyun
---
## [PluginCore.AspNetCore-v1.3.3](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.3.2..PluginCore.AspNetCore-v1.3.3) - 2023-12-14
### Build
- **(src/plugincore.aspnetcore/plugincore.aspnetcore.csproj)** packageReference update, 1.3.2->1.3.3 - ([6fc3d1a](https://github.com/yiyungent/PluginCore/commit/6fc3d1ad6359a5f8690babfc6949e0821d5bd6c5)) - yiyun
---
## [PluginCore.AspNetCore-v1.3.2](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.3.1..PluginCore.AspNetCore-v1.3.2) - 2023-08-21
### Features
- **(src/plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** services: PluginFinder - ([144db05](https://github.com/yiyungent/PluginCore/commit/144db05576001a72f9cbd6beea0b3b5baa6a082c)) - yiyun
### Build
- **(src/plugincore.aspnetcore/plugincore.aspnetcore.csproj)** <Version>1.3.2</Version> - ([dfdd17d](https://github.com/yiyungent/PluginCore/commit/dfdd17dbcb2fc636cc102ab76497ff5768c64a4e)) - yiyun
---
## [PluginCore.AspNetCore-v1.3.1](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.3.0..PluginCore.AspNetCore-v1.3.1) - 2023-02-15
### Build
- **(plugincore.aspnetcore.csproj)** `<Version>1.3.1</Version>` - ([39e0d03](https://github.com/yiyungent/PluginCore/commit/39e0d037c6f3e0dcf4845b5ca8ae2aa41142a474)) - yiyun
---
## [PluginCore.AspNetCore-v1.3.0](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.2.0..PluginCore.AspNetCore-v1.3.0) - 2023-02-15
### Bug Fixes
- **(plugincore.aspnetcore,plugincore)** iList<string> EnabledPlugins->List<string>,IList不支持Remove - ([4d5d30e](https://github.com/yiyungent/PluginCore/commit/4d5d30e66c4c28998a7a6ac96bf3ffb25e4872b4)) - yiyun
### Features
- **(plugincore.aspnetcore,plugincore.iplugins,plugincore)** 仅保留已启用/已禁用 状态, IPlugin新方法 - ([e843a5b](https://github.com/yiyungent/PluginCore/commit/e843a5ba9fad4e88290c09bb3282b730c44c5a06)) - yiyun
### Build
- **(src/plugincore.aspnetcore/plugincore.aspnetcore.csproj)** `<Version>1.3.0</Version>` - ([64b9775](https://github.com/yiyungent/PluginCore/commit/64b977569312539aa2775235ae1f0fca5517ddcd)) - yiyun
---
## [PluginCore.AspNetCore-v1.2.0](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.1.0..PluginCore.AspNetCore-v1.2.0) - 2023-02-14
### Features
- **(src/plugincore.aspnetcore/controllers/debugcontroller.cs)** pluginContexts:PluginId - ([f82b7b1](https://github.com/yiyungent/PluginCore/commit/f82b7b199420b15fc6f38e8f26c8094ee4be1b88)) - yiyun
### Build
- **(src/plugincore.aspnetcore/plugincore.aspnetcore.csproj)** <Version>1.2.0</Version> - ([fe418e0](https://github.com/yiyungent/PluginCore/commit/fe418e0f67dd8ed1fd2d78dc3f4106c8dc1a396b)) - yiyun
---
## [PluginCore.AspNetCore-v1.1.0](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.0.4..PluginCore.AspNetCore-v1.1.0) - 2023-02-10
### Features
- **(src/plugincore.aspnetcore/)** update - ([52ee48f](https://github.com/yiyungent/PluginCore/commit/52ee48fe234040fd0cc0a4d21f3c2e1c9483735e)) - yiyun
- **(src/plugincore.aspnetcore/controllers/debugcontroller.cs)** add - ([196823d](https://github.com/yiyungent/PluginCore/commit/196823d6e3b39eb70d1d4bb55d7d4c8433ee64fc)) - yiyun
- **(src/plugincore.aspnetcore/controllers/debugcontroller.cs)** 完成 - ([2a2e213](https://github.com/yiyungent/PluginCore/commit/2a2e2131ee4b25912c16ca0d01ef408702cbfc39)) - yiyun
- **(src/plugincore.aspnetcore/controllers/debugcontroller.cs)** services - ([22edc22](https://github.com/yiyungent/PluginCore/commit/22edc229c1bedc4667a920ef62c11c8e002ba9d2)) - yiyun
### Build
- **(src/plugincore.aspnetcore/plugincore.aspnetcore.csproj)** <Version>1.1.0</Version> - ([3bdb176](https://github.com/yiyungent/PluginCore/commit/3bdb17602c3258386711b200917e567c82dc442b)) - yiyun
---
## [PluginCore.AspNetCore-v1.0.4](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.0.3..PluginCore.AspNetCore-v1.0.4) - 2023-01-12
### Build
- **(plugincore.aspnetcore.csproj)** <Version>1.0.4</Version> - ([46cf5dd](https://github.com/yiyungent/PluginCore/commit/46cf5dd5f955f6b648323e5216af97dd177e5a4b)) - yiyun
---
## [PluginCore.AspNetCore-v1.0.3](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.0.2..PluginCore.AspNetCore-v1.0.3) - 2022-06-03
### Bug Fixes
- **(backgroundservices/plugintimejobbackgroundservice.cs)** 定时任务:强制GC回收,抑制内存++ - ([434e824](https://github.com/yiyungent/PluginCore/commit/434e82403b6aa2050945eeaa1131ea7965bdbc5f)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** <Version>1.0.3</Version> - ([7da5638](https://github.com/yiyungent/PluginCore/commit/7da5638672571ca0ef057ebf73493260886b903e)) - yiyun
---
## [PluginCore.AspNetCore-v1.0.2](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.0.1..PluginCore.AspNetCore-v1.0.2) - 2022-04-19
### Features
- **(plugincore.aspnetcore)** pluginsController: 移除: 插件上传大小限制 - ([90f8d67](https://github.com/yiyungent/PluginCore/commit/90f8d671a840ae8ca3688f4a5cbc22e568a6159e)) - yiyun
### Style
- add: copyright: *.cs - ([9643dce](https://github.com/yiyungent/PluginCore/commit/9643dce112861a440d63306cb555accbed3d5111)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** <Version>1.0.2</Version> - ([b4eb1cc](https://github.com/yiyungent/PluginCore/commit/b4eb1cca9039ff95a516ba6d4000f10d156e03d0)) - yiyun
- **(plugincore.aspnetcore/package.json,package-lock.json)** "plugincore-admin-frontend": "0.3.2" - ([7a70b20](https://github.com/yiyungent/PluginCore/commit/7a70b2003128bf569b4fdd4b7eaa662a92da8ee8)) - yiyun
---
## [PluginCore.AspNetCore-v1.0.1](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.0.0..PluginCore.AspNetCore-v1.0.1) - 2022-04-17
### Build
- **(plugincore.aspnetcore.csproj)** <Version>1.0.1</Version> - ([ab348ce](https://github.com/yiyungent/PluginCore/commit/ab348ceca937f1b1f4ba8c83f4816b9a0ae7ea3e)) - yiyun
---
## [PluginCore.AspNetCore-v1.0.0](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v0.0.5..PluginCore.AspNetCore-v1.0.0) - 2022-04-17
### Bug Fixes
- **(plugincore)** 临时修复由于 PluginContextManager 单例失败 导致的插件信息丢失 - ([fa613b4](https://github.com/yiyungent/PluginCore/commit/fa613b4c46e41c906fe955eb8f62c3f4937795bc)) - yiyun
### Features
- **(plugincore,plugincore.aspnetcore)** aspNetCorePluginManagerBeta,PluginLoadContext,PluginFinder - ([9d65a59](https://github.com/yiyungent/PluginCore/commit/9d65a590e3e0850251f6d815c322c7c5d9c7cf3f)) - yiyun
- **(plugincore.aspnetcore)** add:DebugController.PluginContext - ([cd8de63](https://github.com/yiyungent/PluginCore/commit/cd8de636c8d7e2b125e11c6ce4091567cb97d4bc)) - yiyun
- **(plugincore.aspnetcore)** commonResponseModel -> BaseResponseModel - ([1a0e834](https://github.com/yiyungent/PluginCore/commit/1a0e834b7dfdb45c45770d42b1733f1cb449a6ca)) - yiyun
### Refactoring
- **(plugincore.aspnetcore,plugincore)** 未完成 - ([a151bcd](https://github.com/yiyungent/PluginCore/commit/a151bcda125cb7e9b5fe11d44e1389afa7a1db5e)) - yiyun
- **(plugincore.aspnetcore,plugincore)** 重构v2: 未测试 - ([53dde31](https://github.com/yiyungent/PluginCore/commit/53dde31116bd6455d33f7d7006b6fd1430f3694b)) - yiyun
- **(plugincore.aspnetcore,plugincore)** 变量名,属性名,类名规范化 - ([eaadabf](https://github.com/yiyungent/PluginCore/commit/eaadabfd759228da245af1d9bd5b86e557540d28)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** <Version>1.0.0</Version> - ([9b5caf4](https://github.com/yiyungent/PluginCore/commit/9b5caf4c4cdc543025c5493d35275a836cae61a9)) - yiyun
---
## [PluginCore.AspNetCore-v0.0.5](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v0.0.4..PluginCore.AspNetCore-v0.0.5) - 2022-04-16
### Build
- **(plugincore.aspnetcore.csproj)** <Version>0.0.5</Version> - ([3e5cb09](https://github.com/yiyungent/PluginCore/commit/3e5cb0921a9faa33f93169c2d983011a1e8c5b5f)) - yiyun
---
## [PluginCore.AspNetCore-v0.0.4](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v0.0.3..PluginCore.AspNetCore-v0.0.4) - 2022-04-16
### Build
- **(plugincore.aspnetcore.csproj)** <Version>0.0.4</Version> - ([adffab3](https://github.com/yiyungent/PluginCore/commit/adffab3fecee6696709c1d325b8c11e5a5389031)) - yiyun
---
## [PluginCore.AspNetCore-v0.0.3](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v0.0.2..PluginCore.AspNetCore-v0.0.3) - 2022-04-16
### Bug Fixes
- **(plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** pluginFinder:TryAddTransient - ([03e9235](https://github.com/yiyungent/PluginCore/commit/03e9235c3c7fe68a3209cb1109792869e78aaa4e)) - yiyun
- **(plugincore.aspnetcore/extensions/plugincorestartupextensions.cs)** 注释错误: 在程序启动时加载所有 已安装并启用 的插件 - ([844826a](https://github.com/yiyungent/PluginCore/commit/844826a630f99c2d866a98e6556404d4b1857e52)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** <Version>0.0.3</Version> - ([ee81a45](https://github.com/yiyungent/PluginCore/commit/ee81a4536af17a85bbebc07bc0c980af8c00ad63)) - yiyun
---
## [PluginCore.AspNetCore-v0.0.2](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v0.0.1..PluginCore.AspNetCore-v0.0.2) - 2022-04-16
### Refactoring
- 1.提取出 PluginCore.AspNetCore,PluginCore.IPlugins.AspNetCore 2.提取出更多接口,可自由替换 - ([fffd8d9](https://github.com/yiyungent/PluginCore/commit/fffd8d91c23fd6e4a4d09cbf91975beb3cf7acf0)) - yiyun
### Build
- **(plugincore.aspnetcore.csproj)** <Version>0.0.2</Version> - ([ba7b274](https://github.com/yiyungent/PluginCore/commit/ba7b27496a4ba784c271305489b9f2f8e6f74ad3)) - yiyun
---
## [PluginCore.AspNetCore-v0.0.1](https://github.com/yiyungent/PluginCore/compare/PluginCore-v0.9.3..PluginCore.AspNetCore-v0.0.1) - 2022-03-26
### Features
- **(plugincore.aspnetcore)** pluginCore.AspNetCore,PluginCore.AspNetCore-nuget-push.yml - ([491f63e](https://github.com/yiyungent/PluginCore/commit/491f63e3362129a2239d87b090aa04cc2e414e9a)) - yiyun
<!-- generated by git-cliff -->

View File

@ -1,207 +0,0 @@
//===================================================
// License: GNU LGPLv3
// Contributors: yiyungent@gmail.com
// Project: https://yiyungent.github.io/PluginCore
// GitHub: https://github.com/yiyungent/PluginCore
//===================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using PluginCore;
using PluginCore.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using PluginCore.AspNetCore.Authorization;
using PluginCore.AspNetCore.ResponseModel;
//using ResponseModel;
namespace PluginCore.AspNetCore.Controllers
{
/// <summary>
/// 应用中心
/// <para>插件</para>
/// </summary>
[Route("api/plugincore/admin/[controller]/[action]")]
[PluginCoreAdminAuthorize]
[ApiController]
public class AppCenterController : ControllerBase
{
#region Fields
private static Dictionary<string, Task> _pluginDownloadTasks;
#endregion
#region Ctor
static AppCenterController()
{
_pluginDownloadTasks = new Dictionary<string, Task>();
}
public AppCenterController()
{
}
#endregion
#region Actions
#region
/// <summary>
/// 插件
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Plugins(string query = "")
{
BaseResponseModel responseDTO = new BaseResponseModel();
IList<PluginRegistryResponseModel> pluginRegistryModels = new List<PluginRegistryResponseModel>();
try
{
// 1. TODO: 从json文件中读取插件订阅源 registry url
string registryUrl = "";
// 2. TODO: 向订阅源发送 http get 获取插件列表信息 eg: http://rem-core-plugins-registry.moeci.com/?query=xxx
IList<string> remotePluginIds = new List<string>();
// 3. 根据本地已有 PluginId 插件情况 状态赋值
PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create();
// IList<string> localPluginIds = pluginConfigModel.EnabledPlugins.Concat(pluginConfigModel.DisabledPlugins).Concat(pluginConfigModel.UninstalledPlugins).ToList();
IList<string> localPluginIds = PluginPathProvider.AllPluginFolderName();
responseDTO.Code = 1;
responseDTO.Message = "获取远程插件数据成功";
responseDTO.Data = pluginRegistryModels;
}
catch (Exception ex)
{
responseDTO.Code = -1;
responseDTO.Message = "获取远程插件数据失败: " + ex.Message;
responseDTO.Data = pluginRegistryModels;
}
return await Task.FromResult(responseDTO);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> DownloadPlugin(string pluginDownloadUrl = "")
{
BaseResponseModel responseDTO = new BaseResponseModel();
#region
if (string.IsNullOrEmpty(pluginDownloadUrl))
{
responseDTO.Code = -1;
responseDTO.Message = "插件下载地址不正确";
return responseDTO;
}
// TODO: 效验是否本地已经存在相同pluginId的插件
#endregion
try
{
// 1.执行下载操作, TODO:存在问题,阻塞对性能不好,但不阻塞又不好通知用户插件下载进度,以及可能存在在插件下载过程中,用户再次点击下载
WebClient webClient = new WebClient();
// TODO: 插件下载文件路径
string pluginDownloadFilePath = "";
//webClient.DownloadFileAsync(new Uri(pluginDownloadFilePath), "");
Task task = webClient.DownloadFileTaskAsync(pluginDownloadUrl, pluginDownloadFilePath);
_pluginDownloadTasks.Add(pluginDownloadUrl, task);
webClient.DownloadFileCompleted += Plugin_DownloadFileCompleted;
webClient.DownloadProgressChanged += Plugin_DownloadProgressChanged;
webClient.Disposed += WebClient_Disposed;
responseDTO.Code = 1;
responseDTO.Message = "开始下载插件";
}
catch (Exception ex)
{
responseDTO.Code = -1;
responseDTO.Message = "下载插件失败: " + ex.Message;
}
return await Task.FromResult(responseDTO);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> DownloadPluginProgress()
{
BaseResponseModel responseDTO = new BaseResponseModel();
try
{
responseDTO.Data = new { };
responseDTO.Code = 1;
responseDTO.Message = "获取插件下载进度成功";
}
catch (Exception ex)
{
responseDTO.Code = -1;
responseDTO.Message = "获取插件下载进度失败: " + ex.Message;
}
return await Task.FromResult(responseDTO);
}
#endregion
#endregion
#region Helpers
/// <summary>
/// 插件下载完成
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Plugin_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
Utils.LogUtil.Info<AppCenterController>("插件下载完成");
// 1.从 _pluginDownloadTasks 中移除
//_pluginDownloadTasks.Remove();
// 2. 解压插件
}
private void Plugin_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
Utils.LogUtil.Info<AppCenterController>($"插件下载进度改变: {e.ProgressPercentage}% {e.BytesReceived}/{e.TotalBytesToReceive}");
}
private void WebClient_Disposed(object sender, EventArgs e)
{
if (sender is WebClient webClient)
{
Utils.LogUtil.Info<AppCenterController>(webClient.BaseAddress);
}
Utils.LogUtil.Info<AppCenterController>(nameof(WebClient_Disposed) + ": " + sender.ToString());
}
#endregion
}
}

View File

@ -1,280 +0,0 @@
using System.Runtime.CompilerServices;
//===================================================
// License: GNU LGPLv3
// Contributors: yiyungent@gmail.com
// Project: https://yiyungent.github.io/PluginCore
// GitHub: https://github.com/yiyungent/PluginCore
//===================================================
using Microsoft.AspNetCore.Mvc;
using PluginCore.AspNetCore.Authorization;
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using System.Runtime.Loader;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;
using PluginCore.AspNetCore.Extensions;
namespace PluginCore.AspNetCore.Controllers
{
/// <summary>
/// [ASP.NET Core — 依赖注入\_啊晚的博客-CSDN博客\_asp.net core 依赖注入](https://blog.csdn.net/weixin_37648525/article/details/127942292)
/// [ASP.NET Core中的依赖注入3: 服务的注册与提供 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-register.html)
/// [ASP.NET Core中的依赖注入5: ServiceProvider实现揭秘 【总体设计 】 - Artech - 博客园](https://www.cnblogs.com/artech/p/asp-net-core-di-service-provider-1.html)
/// [dotnet/ServiceProvider.cs at main · dotnet/dotnet](https://github.com/dotnet/dotnet/blob/main/src/runtime/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs)
/// [Net6 DI源码分析Part2 Engine,ServiceProvider - 一身大膘 - 博客园](https://www.cnblogs.com/hts92/p/15800990.html)
/// [【特别的骚气】asp.net core运行时注入服务实现类库热插拔 - 四处观察 - 博客园](https://www.cnblogs.com/1996-Chinese-Chen/p/16154218.html)
///
/// ActivatorUtilities.CreateInstance<PluginCore.IPlugins.IPlugin>(serviceProvider, "test");
/// ActivatorUtilities.GetServiceOrCreateInstance<PluginCore.IPlugins.IPlugin>(serviceProvider);
///
/// </summary>
[Route("api/plugincore/admin/[controller]/[action]")]
[PluginCoreAdminAuthorize]
[ApiController]
public class DebugController : ControllerBase
{
#region Fields
private readonly IPluginContextManager _pluginContextManager;
#endregion
#region Ctor
public DebugController(IPluginContextManager pluginContextManager)
{
_pluginContextManager = pluginContextManager;
}
#endregion
#region Actions
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> PluginContexts()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
var pluginContextList = _pluginContextManager.All();
Dictionary<string, List<string>> keyValuePairs = new Dictionary<string, List<string>>();
foreach (var pluginContext in pluginContextList)
{
keyValuePairs.Add($"{pluginContext.GetType().ToString()} - {pluginContext.PluginId} - {pluginContext.GetHashCode()}", pluginContext.Assemblies.Select(m => m.FullName).ToList());
}
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = keyValuePairs;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> AssemblyLoadContexts()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
var assemblyLoadContextDefault = AssemblyLoadContext.Default;
var assemblyLoadContextAll = AssemblyLoadContext.All;
var responseDataModel = new AssemblyLoadContextsResponseDataModel();
responseDataModel.Default = new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel
{
Name = assemblyLoadContextDefault.Name,
Type = assemblyLoadContextDefault.GetType().ToString(),
Assemblies = assemblyLoadContextDefault.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList()
};
responseDataModel.All = assemblyLoadContextAll.Select(item => new AssemblyLoadContextsResponseDataModel.AssemblyLoadContextModel
{
Name = item.Name,
Type = item.GetType().ToString(),
Assemblies = item.Assemblies.Select(m => new AssemblyModel { FullName = m.FullName, DefinedTypes = m.DefinedTypes.Select(m => m.FullName).ToList() }).ToList()
}).ToList();
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = responseDataModel;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Assemblies()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
List<AssemblyModel> assemblyModels = new List<AssemblyModel>();
foreach (var item in assemblies)
{
assemblyModels.Add(new AssemblyModel
{
FullName = item.FullName,
DefinedTypes = item.DefinedTypes.Select(m => m.FullName).ToList()
});
}
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = assemblyModels;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Services([FromServices] IServiceProvider serviceProvider)
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
//IServiceProvider serviceProvider = HttpContext.RequestServices;
//var provider = serviceProvider.GetType().GetProperty("RootProvider", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
//var serviceField = provider.GetType().GetField("_realizedServices", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
//var serviceValue = serviceField.GetValue(provider);
//var funcType = serviceField.FieldType.GetGenericArguments()[1].GetGenericArguments()[0];
//ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object?>> realizedServices = (ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object?>>)serviceValue;
// 获取所有已经注册的服务
var allService = serviceProvider.GetAllServiceDescriptors();
List<ServiceModel> serviceModels = new List<ServiceModel>();
foreach (var item in allService)
{
serviceModels.Add(new ServiceModel
{
Type = item.Key.ToString(),
ImplementationType = item.Value.ImplementationType?.ToString() ?? "",
Lifetime = item.Value.Lifetime.ToString(),
TypeAssembly = new AssemblyModel
{
FullName = item.Key.Assembly.FullName,
},
ImplementationTypeAssembly = new AssemblyModel
{
FullName = item.Value.ImplementationType?.Assembly?.FullName ?? ""
}
});
}
responseModel.Code = 1;
responseModel.Message = "success";
responseModel.Data = serviceModels;
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "error";
responseModel.Data = ex.ToString();
}
return await Task.FromResult(responseModel);
}
#endregion
public sealed class AssemblyLoadContextsResponseDataModel
{
public AssemblyLoadContextModel Default
{
get; set;
}
public List<AssemblyLoadContextModel> All
{
get; set;
}
public sealed class AssemblyLoadContextModel
{
public string Name
{
get; set;
}
public string Type
{
get; set;
}
public List<AssemblyModel> Assemblies
{
get; set;
}
}
}
public sealed class AssembliesResponseDataModel
{
}
public sealed class ServiceModel
{
public string Type
{
get; set;
}
public string ImplementationType
{
get; set;
}
public string Lifetime
{
get; set;
}
public AssemblyModel TypeAssembly
{
get; set;
}
public AssemblyModel ImplementationTypeAssembly
{
get; set;
}
}
public sealed class AssemblyModel
{
public string FullName
{
get; set;
}
public List<string> DefinedTypes
{
get; set;
}
}
}
}

View File

@ -1,73 +0,0 @@
//===================================================
// License: GNU LGPLv3
// Contributors: yiyungent@gmail.com
// Project: https://yiyungent.github.io/PluginCore
// GitHub: https://github.com/yiyungent/PluginCore
//===================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
namespace PluginCore.AspNetCore.Controllers
{
[Controller]
public class HomeController : Controller
{
#region Old
//private readonly IWebHostEnvironment _webHostEnvironment;
//public bool IsLocalFronted
//{
// get
// {
// return PluginCore.Config.PluginCoreConfigFactory.Create().IsLocalFrontend;
// }
//}
//public string RemoteFronted
//{
// get
// {
// return PluginCore.Config.PluginCoreConfigFactory.Create().RemoteFrontend;
// }
//}
//public HomeController(IWebHostEnvironment webHostEnvironment)
//{
// this._webHostEnvironment = webHostEnvironment;
//}
//[Route("PluginCore/Admin")]
//public async Task<ActionResult> Home()
//{
// if (this.IsLocalFronted)
// {
// var localIndexFilePath = Path.Combine(
// this._webHostEnvironment.ContentRootPath, "PluginCoreAdmin", "index.html");
// return PhysicalFile(localIndexFilePath, "text/html");
// }
// else
// {
// string htmlStr = string.Empty;
// HttpClient httpClient = new HttpClient();
// htmlStr = await httpClient.GetStringAsync(this.RemoteFronted + "/index.html");
// return Content(htmlStr, "text/html", Encoding.UTF8);
// }
//}
#endregion
}
}

View File

@ -1,103 +0,0 @@
//===================================================
// License: GNU LGPLv3
// Contributors: yiyungent@gmail.com
// Project: https://yiyungent.github.io/PluginCore
// GitHub: https://github.com/yiyungent/PluginCore
//===================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using PluginCore;
using PluginCore.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using PluginCore.AspNetCore.Authorization;
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.IPlugins;
using System.Text;
using PluginCore.Interfaces;
namespace PluginCore.AspNetCore.Controllers
{
[Route("api/plugincore/[controller]/[action]")]
[ApiController]
public class PluginWidgetController : ControllerBase
{
#region Fields
private readonly IPluginFinder _pluginFinder;
#endregion
#region Ctor
public PluginWidgetController(IPluginFinder pluginFinder)
{
_pluginFinder = pluginFinder;
}
#endregion
#region Actions
#region Widget
/// <summary>
/// Widget
/// </summary>
/// <returns></returns>
[HttpGet, HttpPost]
//public async Task<ActionResult<CommonResponseModel>> Widget(string widgetKey, string extraPars = "")
public async Task<ActionResult> Widget(string widgetKey, string extraPars = "")
{
BaseResponseModel responseModel = new ResponseModel.BaseResponseModel();
string responseData = "";
widgetKey = widgetKey.Trim('"', '\'');
string[] extraParsArr = null;
if (!string.IsNullOrEmpty(extraPars))
{
extraParsArr = extraPars.Split(",", StringSplitOptions.RemoveEmptyEntries);
extraParsArr = extraParsArr.Select(m => m.Trim('"', '\'')).ToArray();
}
StringBuilder sb = new StringBuilder();
sb.AppendLine($"<!-- start:PluginCore.IPlugins.IWidgetPlugin.Widget({widgetKey},{extraPars}) -->");
try
{
List<IWidgetPlugin> plugins = this._pluginFinder.EnablePlugins<IWidgetPlugin>().ToList();
foreach (var item in plugins)
{
string widgetStr = await item.Widget(widgetKey, extraParsArr);
if (!string.IsNullOrEmpty(widgetStr))
{
// TODO: 配合 PluginCoreConfig.PluginWidgetDebug
// TODO: PluginCoreConfig 改为 Options 模式, 避免手动反复读取文件 效率低
//sb.AppendLine($"<!-- {item.GetType().ToString()}: -->");
sb.AppendLine(widgetStr);
}
}
}
catch (Exception ex)
{
Utils.LogUtil.Error<PluginWidgetController>(ex, ex.Message);
sb.AppendLine($"<!-- Exception: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}, Details: Console -->");
}
sb.AppendLine($"<!-- end:PluginCore.IPlugins.IWidgetPlugin.Widget({widgetKey},{extraPars}) -->");
responseData = sb.ToString();
responseModel.Code = 1;
responseModel.Message = "Load Widget Success";
responseModel.Data = responseData;
//return await Task.FromResult(responseModel);
return Content(responseData, "text/html;charset=utf-8");
}
#endregion
#endregion
}
}

View File

@ -1,688 +0,0 @@
//===================================================
// License: GNU LGPLv3
// Contributors: yiyungent@gmail.com
// Project: https://yiyungent.github.io/PluginCore
// GitHub: https://github.com/yiyungent/PluginCore
//===================================================
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
//using Core.Common;
//using Framework.Authorization;
using PluginCore;
using PluginCore.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using PluginCore.AspNetCore.Authorization;
using PluginCore.Infrastructure;
using PluginCore.IPlugins;
using PluginCore.AspNetCore.ResponseModel;
using PluginCore.Interfaces;
using PluginCore.AspNetCore.Interfaces;
//using ResponseModel;
namespace PluginCore.AspNetCore.Controllers
{
[Route("api/plugincore/admin/[controller]/[action]")]
[PluginCoreAdminAuthorize]
[ApiController]
public class PluginsController : ControllerBase
{
#region Fields
private readonly IPluginManager _pluginManager;
private readonly IPluginFinder _pluginFinder;
private readonly IPluginApplicationBuilderManager _pluginApplicationBuilderManager;
#endregion
#region Ctor
public PluginsController(IPluginManager pluginManager, IPluginFinder pluginFinder, IPluginApplicationBuilderManager pluginApplicationBuilderManager)
{
_pluginManager = pluginManager;
_pluginFinder = pluginFinder;
_pluginApplicationBuilderManager = pluginApplicationBuilderManager;
}
#endregion
#region Actions
#region
/// <summary>
/// 加载插件列表
/// </summary>
/// <param name="status">插件状态</param>
/// <returns></returns>
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> List(string status = "all")
{
BaseResponseModel responseData = new ResponseModel.BaseResponseModel();
var pluginConfigModel = PluginConfigModelFactory.Create();
// 获取所有插件信息
IList<PluginInfoModel> pluginInfoModels = PluginInfoModelFactory.CreateAll();
IList<PluginInfoResponseModel> responseModels = new List<PluginInfoResponseModel>();
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
// 添加插件状态
responseModels = PluginInfoModelToResponseModel(pluginInfoModels, pluginConfigModel, enablePluginIds);
#region
switch (status.ToLower())
{
case "all":
break;
case "enabled":
responseModels = responseModels.Where(m => m.Status == PluginStatus.Enabled).ToList();
break;
case "disabled":
responseModels = responseModels.Where(m => m.Status == PluginStatus.Disabled).ToList();
break;
default:
break;
}
#endregion
responseData.Code = 1;
responseData.Message = "加载插件列表成功";
responseData.Data = responseModels;
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Uninstall(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
pluginId = pluginId.Trim();
var pluginConfigModel = PluginConfigModelFactory.Create();
// 卸载插件 必须 先禁用插件
#region
if (pluginConfigModel.EnabledPlugins.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = "卸载失败: 请先禁用此插件";
return await Task.FromResult(responseData);
}
string pluginDirStr= Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId);
string pluginWwwrootDirStr = Path.Combine(PluginPathProvider.PluginsWwwRootDir(), pluginId);
if (!Directory.Exists(pluginDirStr) && !Directory.Exists(pluginWwwrootDirStr))
{
responseData.Code = -2;
responseData.Message = "卸载失败: 此插件不存在";
return await Task.FromResult(responseData);
}
#endregion
try
{
// PS:卸载插件必须先禁用插件所以此时插件LoadContext已被移除释放(插件Assemblies已被释放), 此处不需移除LoadContext
// 1.删除物理文件
var pluginDir = new DirectoryInfo(pluginDirStr);
if (pluginDir.Exists) {
pluginDir.Delete(true);
}
// 虽然 已禁用 时 pluginWwwrootDirStr/pluginId 已删除, 但为确保, 还是再删除一次
var pluginWwwrootDir = new DirectoryInfo(pluginWwwrootDirStr);
if (pluginWwwrootDir.Exists) {
pluginWwwrootDir.Delete(true);
}
responseData.Code = 1;
responseData.Message = "卸载成功";
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "卸载失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Enable(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
var pluginConfigModel = PluginConfigModelFactory.Create();
// 效验是否存在于 已禁用插件列表
#region
pluginId = pluginId.Trim();
var pluginDir = new DirectoryInfo(Path.Combine(PluginPathProvider.PluginsRootPath(), pluginId));
if (pluginDir != null && !pluginDir.Exists)
{
responseData.Code = -1;
responseData.Message = "启用失败: 此插件不存在";
return await Task.FromResult(responseData);
}
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
if (enablePluginIds.Contains(pluginId)) {
responseData.Code = -2;
responseData.Message = "启用失败: 此插件已启用";
return await Task.FromResult(responseData);
}
#endregion
try
{
// 1. 创建插件程序集加载上下文, 添加到 PluginsLoadContexts
_pluginManager.LoadPlugin(pluginId);
// 2. 添加到 pluginConfigModel.EnabledPlugins
pluginConfigModel.EnabledPlugins.Add(pluginId);
// 4.保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
// 5. 找到此插件实例
IPlugin plugin = _pluginFinder.Plugin(pluginId);
if (plugin == null)
{
// 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json
try
{
_pluginManager.UnloadPlugin(pluginId);
}
catch (Exception ex)
{ }
// 从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
responseData.Code = -1;
responseData.Message = "启用失败: 此插件不存在";
return await Task.FromResult(responseData);
}
// 6.调取插件的 AfterEnable(), 插件开发者可在此回收资源
var pluginEnableResult = plugin.AfterEnable();
if (!pluginEnableResult.IsSuccess)
{
// 7.启用不成功, 回滚插件状态: (1)释放插件上下文 (2)更新 plugin.config.json
try
{
_pluginManager.UnloadPlugin(pluginId);
}
catch (Exception ex)
{ }
// 从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
responseData.Code = -1;
responseData.Message = "启用失败: 来自插件的错误信息: " + pluginEnableResult.Message;
return await Task.FromResult(responseData);
}
// 7. ReBuild
this._pluginApplicationBuilderManager.ReBuild();
// 8. 尝试复制 插件下的 wwwroot 到 Plugins_wwwroot
string wwwRootDir = PluginPathProvider.WwwRootDir(pluginId);
if (Directory.Exists(wwwRootDir))
{
string targetDir = PluginPathProvider.PluginWwwRootDir(pluginId);
Utils.FileUtil.CopyFolder(wwwRootDir, targetDir);
}
responseData.Code = 1;
// responseData.Message = "启用成功";
responseData.Message = pluginEnableResult.Message;
}
catch (Exception ex)
{
responseData.Code = -2;
responseData.Message = "启用失败: " + ex.Message;
Utils.LogUtil.Error<PluginsController>(ex, ex.Message);
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Disable(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
#region
pluginId = pluginId.Trim();
var pluginConfigModel = PluginConfigModelFactory.Create();
// string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
// // 效验是否存在于 已启用插件列表
// if (!enablePluginIds.Contains(pluginId))
// {
// responseData.Code = -1;
// responseData.Message = "禁用失败: 此插件不存在, 或未启用";
// return await Task.FromResult(responseData);
// }
#endregion
try
{
// 1. 找到此插件实例
IPlugin plugin = _pluginFinder.Plugin(pluginId);
if (plugin == null)
{
responseData.Code = -1;
responseData.Message = "禁用失败: 此插件不存在, 或未启用";
return await Task.FromResult(responseData);
}
string pluginDisableResultMessage = "";
try
{
// 2.调取插件的 BeforeDisable(), 插件开发者可在此回收资源
var pluginDisableResult = plugin.BeforeDisable();
pluginDisableResultMessage = pluginDisableResult.Message;
if (!pluginDisableResult.IsSuccess)
{
responseData.Code = -1;
responseData.Message = "禁用失败: 来自插件的错误信息: " + pluginDisableResult.Message;
return await Task.FromResult(responseData);
}
// 3.移除插件对应的程序集加载上下文
_pluginManager.UnloadPlugin(pluginId);
// 3.1. ReBuild
this._pluginApplicationBuilderManager.ReBuild();
if (pluginConfigModel.EnabledPlugins.Contains(pluginId)) {
// 4.从 pluginConfigModel.EnabledPlugins 移除
pluginConfigModel.EnabledPlugins.Remove(pluginId);
// 5.保存到 plugin.config.json
PluginConfigModelFactory.Save(pluginConfigModel);
}
}
catch (Exception ex)
{
Utils.LogUtil.Error<PluginsController>(ex, ex.Message);
responseData.Code = -1;
responseData.Message = "禁用失败: 此插件不存在, 或未启用";
return await Task.FromResult(responseData);
}
// 7. 尝试移除 Plugins_wwwroot/PluginId
string pluginWwwRootDir = PluginPathProvider.PluginWwwRootDir(pluginId);
if (Directory.Exists(pluginWwwRootDir))
{
Directory.Delete(pluginWwwRootDir, true);
}
responseData.Code = 1;
// responseData.Message = "禁用成功";
responseData.Message = pluginDisableResultMessage;
}
catch (Exception ex)
{
responseData.Code = -2;
responseData.Message = "禁用失败: " + ex.Message;
Utils.LogUtil.Error<PluginsController>(ex, ex.Message);
}
return await Task.FromResult(responseData);
}
#endregion
#region
/// <summary>
/// 上传插件
/// </summary>
/// <param name="file">注意: 参数名一定为 file 对应前端传过来时以 file 为名</param>
/// <returns></returns>
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Upload(IFormFile file)
{
BaseResponseModel responseData = new BaseResponseModel();
#region
if (file == null)
{
responseData.Code = -1;
responseData.Message = "上传的文件不能为空";
return responseData;
}
//文件后缀
string fileExtension = Path.GetExtension(file.FileName);//获取文件格式,拓展名
// 类型标记
UploadFileType uploadFileType = UploadFileType.NoAllowedType;
switch (fileExtension)
{
case ".zip":
uploadFileType = UploadFileType.Zip;
break;
case ".nupkg":
uploadFileType = UploadFileType.Nupkg;
break;
}
if (fileExtension != ".zip" && fileExtension != ".nupkg")
{
responseData.Code = -1;
// nupkg 其实就是 zip
responseData.Message = "只能上传 zip 或 nupkg 格式文件";
return responseData;
}
// PluginCore.AspNetCore-v1.0.2 起 不再限制插件上传大小
//判断文件大小
//var fileSize = file.Length;
//if (fileSize > 1024 * 1024 * 5) // 5M
//{
// responseData.Code = -1;
// responseData.Message = "上传的文件不能大于5MB";
// return responseData;
//}
#endregion
try
{
// 1.先上传到 临时插件上传目录, 用Guid.zip作为保存文件名
string tempZipFilePath = Path.Combine(PluginPathProvider.TempPluginUploadDir(), Guid.NewGuid() + ".zip");
using (var fs = System.IO.File.Create(tempZipFilePath))
{
file.CopyTo(fs); //将上传的文件文件流复制到fs中
fs.Flush();//清空文件流
}
// 2.解压
bool isDecomparessSuccess = false;
if (uploadFileType == UploadFileType.Zip)
{
isDecomparessSuccess = Utils.ZipHelper.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", ""));
}
else if (uploadFileType == UploadFileType.Nupkg)
{
isDecomparessSuccess = NupkgService.DecomparessFile(tempZipFilePath, tempZipFilePath.Replace(".zip", ""));
}
// 3.删除原压缩包
System.IO.File.Delete(tempZipFilePath);
if (!isDecomparessSuccess)
{
responseData.Code = -1;
responseData.Message = "解压插件压缩包失败";
return responseData;
}
// 4.读取其中的info.json, 获取 PluginId 值
PluginInfoModel pluginInfoModel = PluginInfoModelFactory.ReadPluginDir(tempZipFilePath.Replace(".zip", ""));
if (pluginInfoModel == null || string.IsNullOrEmpty(pluginInfoModel.PluginId))
{
// 记得删除已不再需要的临时插件文件夹
Directory.Delete(tempZipFilePath.Replace(".zip", ""), true);
responseData.Code = -1;
responseData.Message = "不合法的插件";
return responseData;
}
string pluginId = pluginInfoModel.PluginId;
// 5.检索 此 PluginId 是否本地插件已存在
var pluginConfigModel = PluginConfigModelFactory.Create();
// 本地已经存在的 PluginId
IList<string> localExistPluginIds = PluginPathProvider.AllPluginFolderName();
if (localExistPluginIds.Contains(pluginId))
{
// 记得删除已不再需要的临时插件文件夹
Directory.Delete(tempZipFilePath.Replace(".zip", ""), true);
responseData.Code = -1;
responseData.Message = $"本地已有此插件 (PluginId: {pluginId}), 请前往插件列表删除后, 再上传";
return responseData;
}
// 6.本地无此插件 -> 移动插件文件夹到 Plugins 下, 并以 PluginId 为插件文件夹名
string pluginsRootPath = PluginPathProvider.PluginsRootPath();
string newPluginDir = Path.Combine(pluginsRootPath, pluginId);
Directory.Move(tempZipFilePath.Replace(".zip", ""), newPluginDir);
// 7. 放入 Plugins 中, 默认为 已禁用
responseData.Code = 1;
responseData.Message = $"上传插件成功 (PluginId: {pluginId})";
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "上传插件失败: " + ex.Message;
ex = ex.InnerException;
while (ex != null)
{
responseData.Message += " - " + ex.InnerException.Message;
ex = ex.InnerException;
}
Utils.LogUtil.Error<PluginsController>(ex, ex.Message);
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Details(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
pluginId = pluginId.Trim();
var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = $"查看详细失败: 不存在 {pluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
PluginInfoModel pluginInfoModel = PluginInfoModelFactory.Create(pluginId);
string[] enablePluginIds = _pluginFinder.EnablePluginIds().ToArray();
PluginInfoResponseModel pluginInfoResponseModel = PluginInfoModelToResponseModel(new List<PluginInfoModel>() { pluginInfoModel }, pluginConfigModel, enablePluginIds).FirstOrDefault();
responseData.Code = 1;
responseData.Message = "查看详细成功";
responseData.Data = pluginInfoResponseModel;
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "查看详细失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Readme(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
pluginId = pluginId.Trim();
// var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = $"查看文档失败: 不存在 {pluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
PluginReadmeModel readmeModel = PluginReadmeModelFactory.Create(pluginId);
PluginReadmeResponseModel readmeResponseModel = new PluginReadmeResponseModel();
readmeResponseModel.Content = readmeModel?.Content ?? "";
readmeResponseModel.PluginId = pluginId;
responseData.Code = 1;
responseData.Message = "查看文档成功";
responseData.Data = readmeResponseModel;
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "查看文档失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#region
[HttpGet]
public async Task<ActionResult<BaseResponseModel>> Settings(string pluginId)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
pluginId = pluginId.Trim();
// var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(pluginId))
{
responseData.Code = -1;
responseData.Message = $"查看设置失败: 不存在 {pluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
string settingsJsonStr = PluginSettingsModelFactory.Create(pluginId);
responseData.Code = 1;
responseData.Message = "查看设置成功";
responseData.Data = settingsJsonStr ?? "无设置项";
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "查看设置失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
[HttpPost]
public async Task<ActionResult<BaseResponseModel>> Settings(PluginSettingsInputModel inputModel)
{
BaseResponseModel responseData = new BaseResponseModel();
try
{
#region
inputModel.PluginId = inputModel.PluginId.Trim();
// var pluginConfigModel = PluginConfigModelFactory.Create();
string[] localPluginIds = PluginPathProvider.AllPluginFolderName().ToArray();
if (!localPluginIds.Contains(inputModel.PluginId))
{
responseData.Code = -1;
responseData.Message = $"设置失败: 不存在 {inputModel.PluginId} 插件";
return await Task.FromResult(responseData);
}
#endregion
inputModel.Data = inputModel.Data ?? "";
PluginSettingsModelFactory.Save(pluginSettingsJsonStr: inputModel.Data, pluginId: inputModel.PluginId);
responseData.Code = 1;
responseData.Message = "设置成功";
responseData.Data = inputModel.Data;
}
catch (Exception ex)
{
responseData.Code = -1;
responseData.Message = "设置失败: " + ex.Message;
}
return await Task.FromResult(responseData);
}
#endregion
#endregion
#region Helpers
[NonAction]
private IList<PluginInfoResponseModel> PluginInfoModelToResponseModel(IList<PluginInfoModel> pluginInfoModels, PluginConfigModel pluginConfigModel, string[] enablePluginIds)
{
// 获取 Plugins 下所有插件
// DirectoryInfo pluginsDir = new DirectoryInfo(PluginPathProvider.PluginsRootPath());
// List<string> pluginIds = pluginsDir?.GetDirectories()?.Select(m => m.Name)?.ToList() ?? new List<string>();
IList<PluginInfoResponseModel> responseModels = new List<PluginInfoResponseModel>();
#region
foreach (var model in pluginInfoModels)
{
PluginInfoResponseModel responseModel = new PluginInfoResponseModel();
responseModel.Author = model.Author;
responseModel.Description = model.Description;
responseModel.DisplayName = model.DisplayName;
responseModel.PluginId = model.PluginId;
responseModel.SupportedVersions = model.SupportedVersions;
responseModel.Version = model.Version;
responseModel.DependPlugins = model.DependPlugins;
if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && !enablePluginIds.Contains(model.PluginId)) {
// 错误情况: 配置 标识 已启用, 但实际没有启用成功
pluginConfigModel.EnabledPlugins.Remove(model.PluginId);
PluginConfigModelFactory.Save(pluginConfigModel);
responseModel.Status = PluginStatus.Disabled;
} else if(!pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId))
{
// 错误情况: 配置没有标识 已启用, 但实际 已启用
pluginConfigModel.EnabledPlugins.Add(model.PluginId);
PluginConfigModelFactory.Save(pluginConfigModel);
responseModel.Status = PluginStatus.Enabled;
} else if (pluginConfigModel.EnabledPlugins.Contains(model.PluginId) && enablePluginIds.Contains(model.PluginId))
{
responseModel.Status = PluginStatus.Enabled;
}
else
{
responseModel.Status = PluginStatus.Disabled;
}
responseModels.Add(responseModel);
}
#endregion
return responseModels;
}
public enum UploadFileType
{
NoAllowedType = 0,
Zip = 1,
Nupkg = 2
}
#endregion
}
}

View File

@ -1,143 +0,0 @@
//===================================================
// License: GNU LGPLv3
// Contributors: yiyungent@gmail.com
// Project: https://yiyungent.github.io/PluginCore
// GitHub: https://github.com/yiyungent/PluginCore
//===================================================
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using PluginCore.AspNetCore.Authorization;
using PluginCore.Config;
using PluginCore.AspNetCore.RequestModel.User;
using PluginCore.AspNetCore.ResponseModel;
namespace PluginCore.AspNetCore.Controllers
{
[Route("api/plugincore/admin/[controller]/[action]")]
[ApiController]
public class UserController : ControllerBase
{
private readonly AccountManager _accountManager;
public string RemoteFronted
{
get
{
return PluginCore.Config.PluginCoreConfigFactory.Create().RemoteFrontend;
}
}
public UserController(AccountManager accountManager)
{
_accountManager = accountManager;
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Login([FromBody] LoginRequestModel requestModel)
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
string token = AccountManager.CreateToken(requestModel.UserName, requestModel.Password);
bool isAdmin = AccountManager.IsAdminToken(token);
if (!isAdmin)
{
responseModel.Code = -1;
responseModel.Message = "用户名或密码不正确";
return await Task.FromResult(responseModel);
}
responseModel.Code = 1;
responseModel.Message = "登录成功";
responseModel.Data = new
{
token = token,
userName = requestModel.UserName
};
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "失败: " + ex.Message;
}
return await Task.FromResult(responseModel);
}
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Logout()
{
BaseResponseModel responseModel = new BaseResponseModel()
{
Code = 1,
Message = "退出登录成功"
};
return await Task.FromResult(responseModel);
}
[PluginCoreAdminAuthorize]
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Info()
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
string adminUserName = PluginCoreConfigFactory.Create().Admin.UserName;
responseModel.Code = 1;
responseModel.Message = "成功";
responseModel.Data = new
{
name = adminUserName,
//avatar = this.RemoteFronted + "/images/avatar.gif"
avatar = ""
};
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "失败: " + ex.Message;
}
return await Task.FromResult(responseModel);
}
[PluginCoreAdminAuthorize]
[HttpGet, HttpPost]
public async Task<ActionResult<BaseResponseModel>> Update([FromBody] UpdateRequestModel requestModel)
{
BaseResponseModel responseModel = new BaseResponseModel();
try
{
PluginCoreConfig pluginCoreConfig = PluginCoreConfigFactory.Create();
pluginCoreConfig.Admin.UserName = requestModel.UserName;
pluginCoreConfig.Admin.Password = requestModel.Password;
PluginCoreConfigFactory.Save(pluginCoreConfig);
responseModel.Code = 1;
responseModel.Message = "修改成功, 需要重新登录";
}
catch (Exception ex)
{
responseModel.Code = -1;
responseModel.Message = "失败: " + ex.Message;
}
return await Task.FromResult(responseModel);
}
}
}

View File

@ -1,158 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
namespace PluginCore.AspNetCore.Extensions
{
public static class IServiceProviderExtensions
{
/// <summary>
/// Get all registered <see cref="ServiceDescriptor"/>
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
public static Dictionary<Type, ServiceDescriptor> GetAllServiceDescriptors(this IServiceProvider provider)
{
var serviceProvider = provider.GetPropertyValue("RootProvider");
var result = new Dictionary<Type, ServiceDescriptor>();
var engine = serviceProvider.GetFieldValue("_engine");
// var callSiteFactory = engine.GetPropertyValue("CallSiteFactory");
var callSiteFactory = serviceProvider.GetPropertyValue("CallSiteFactory");
var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup");
if (descriptorLookup is IDictionary dictionary)
{
foreach (DictionaryEntry entry in dictionary)
{
result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last"));
}
}
return result;
#region Old
//if (provider is ServiceProvider serviceProvider)
//{
// var result = new Dictionary<Type, ServiceDescriptor>();
// var engine = serviceProvider.GetFieldValue("_engine");
// var callSiteFactory = engine.GetPropertyValue("CallSiteFactory");
// var descriptorLookup = callSiteFactory.GetFieldValue("_descriptorLookup");
// if (descriptorLookup is IDictionary dictionary)
// {
// foreach (DictionaryEntry entry in dictionary)
// {
// result.Add((Type)entry.Key, (ServiceDescriptor)entry.Value.GetPropertyValue("Last"));
// }
// }
// return result;
//}
//throw new NotSupportedException($"Type '{provider.GetType()}' is not supported!");
#endregion
}
}
public static class ReflectionHelper
{
// ##########################################################################################
// Get / Set Field
// ##########################################################################################
#region Get / Set Field
public static object GetFieldValue(this object obj, string fieldName)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var fieldInfo = GetFieldInfo(objType, fieldName);
if (fieldInfo == null)
throw new ArgumentOutOfRangeException(fieldName,
$"Couldn't find field {fieldName} in type {objType.FullName}");
return fieldInfo.GetValue(obj);
}
public static void SetFieldValue(this object obj, string fieldName, object val)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var fieldInfo = GetFieldInfo(objType, fieldName);
if (fieldInfo == null)
throw new ArgumentOutOfRangeException(fieldName,
$"Couldn't find field {fieldName} in type {objType.FullName}");
fieldInfo.SetValue(obj, val);
}
private static FieldInfo GetFieldInfo(Type type, string fieldName)
{
FieldInfo fieldInfo = null;
do
{
fieldInfo = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
type = type.BaseType;
} while (fieldInfo == null && type != null);
return fieldInfo;
}
#endregion
// ##########################################################################################
// Get / Set Property
// ##########################################################################################
#region Get / Set Property
public static object GetPropertyValue(this object obj, string propertyName)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var propertyInfo = GetPropertyInfo(objType, propertyName);
if (propertyInfo == null)
throw new ArgumentOutOfRangeException(propertyName,
$"Couldn't find property {propertyName} in type {objType.FullName}");
return propertyInfo.GetValue(obj, null);
}
public static void SetPropertyValue(this object obj, string propertyName, object val)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
Type objType = obj.GetType();
var propertyInfo = GetPropertyInfo(objType, propertyName);
if (propertyInfo == null)
throw new ArgumentOutOfRangeException(propertyName,
$"Couldn't find property {propertyName} in type {objType.FullName}");
propertyInfo.SetValue(obj, val, null);
}
private static PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
PropertyInfo propertyInfo = null;
do
{
propertyInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
type = type.BaseType;
} while (propertyInfo == null && type != null);
return propertyInfo;
}
#endregion
}
}

View File

@ -1,415 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.Models;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.FileProviders;
using PluginCore.AspNetCore.Authorization;
using PluginCore.AspNetCore.AdminUI;
using PluginCore.Infrastructure;
using PluginCore.Interfaces;
using PluginCore.AspNetCore.Middlewares;
using PluginCore.AspNetCore.BackgroundServices;
using PluginCore.IPlugins;
using PluginCore.AspNetCore.Interfaces;
using PluginCore.lmplements;
using PluginCore.AspNetCore.lmplements;
namespace PluginCore.AspNetCore.Extensions
{
/// <summary>
///
/// </summary>
public static class PluginCoreStartupExtensions
{
private static IWebHostEnvironment _webHostEnvironment;
private static IServiceCollection _services;
private static IServiceProvider _serviceProvider;
/// <summary>
/// 若需要替换默认实现, 请在 <see cref="AddPluginCore(IServiceCollection)"/> 之前, 若在之后, 则无法影响 主程序启动时默认行为
/// </summary>
/// <param name="services"></param>
public static void AddPluginCore(this IServiceCollection services)
{
services.AddPluginCoreServices();
services.AddPluginCoreLog();
services.AddPluginCorePlugins();
#region PluginCore Admin
// 1. 先 Authentication (我是谁) 2. 再 Authorization (我能做什么)
// Authentication
//注销内置认证 by tomny
// services.AddPluginCoreAuthentication();
//注销内置授权 by tomny
// Authorization
//services.AddPluginCoreAuthorization();
#endregion
services.AddPluginCoreStartupPlugin();
// AddBackgroundServices
services.AddBackgroundServices();
// 一定要在最后
_services = services;
_serviceProvider = services.BuildServiceProvider();
}
public static IApplicationBuilder UsePluginCore(this IApplicationBuilder app)
{
// 一定在 PluginCore 添加的中间件中 第一个
app.UseMiddleware<PluginHttpStartFilterMiddleware>();
app.UsePluginCoreLanguageMiddleware();
//注销内置管理页面 by tomny
// app.UsePluginCoreAdminUI();
app.UsePluginCoreStaticFiles();
// 发现 UseAuthentication 认证中间件重复添加, 也只会执行一次 认证
// 但 UseAuthorization 重复添加2次, 则会执行 2次 授权
//注销内置认证授权 by tomny
//app.UseAuthentication();
//app.UseAuthorization();
#region Plugin Middleware
// Plugin Middleware
//app.UseMiddleware<PluginContentFilterMiddleware>();
// 一定在 PluginCore 添加的中间件中 最后一个
app.UseMiddleware<PluginHttpEndFilterMiddleware>();
#endregion
app.UsePluginCoreStartupPlugin();
app.UsePluginCoreAppStart();
// Log
app.UsePluginCoreLog();
return app;
}
public static void AddPluginCoreServices(this IServiceCollection services)
{
#region
#region ASP.NET Core
// start: 仅用于 ASP.NET Core
// 用于添加插件Controller 时通知Controller.Action发生变化
services.AddSingleton<IActionDescriptorChangeProvider>(PluginActionDescriptorChangeProvider.Instance);
services.AddSingleton(PluginActionDescriptorChangeProvider.Instance);
services.TryAddTransient<PluginControllerManager>();
services.TryAddTransient<IPluginControllerManager, PluginControllerManager>();
services.TryAddTransient<PluginApplicationBuilderManager>();
services.TryAddTransient<IPluginApplicationBuilderManager, PluginApplicationBuilderManager>();
// end: 仅用于 ASP.NET Core
#endregion
#region
// v1 旧版
//services.TryAddTransient<PluginContextPackV1>();
//services.TryAddTransient<IPluginContextPack, PluginContextPackV1>();
//services.TryAddTransient<AspNetCorePluginManagerV1>();
//services.TryAddTransient<IPluginManager, AspNetCorePluginManagerV1>();
services.TryAddTransient<PluginContextPack>();
services.TryAddTransient<IPluginContextPack, PluginContextPack>();
//注销内置插件管理 by tomny
services.TryAddTransient<AspNetCorePluginManager>();
services.TryAddTransient<IPluginManager, AspNetCorePluginManager>();
services.TryAddTransient<PluginFinderV1>();
services.TryAddTransient<PluginFinderV2>();
services.TryAddTransient<PluginFinder>();
services.TryAddTransient<IPluginFinder, PluginFinder>();
// 注意: 它必须单例
// TODO: 不知道原因, 单例失败, 每次 获取 IPluginFinder 都会获取新的 IPluginContextManager
services.TryAddSingleton<PluginContextManager>();
services.TryAddSingleton<IPluginContextManager, PluginContextManager>();
#endregion
#endregion
// ************************* 注意: IServiceCollection 是服务列表, 但由 IServiceProvider 来负责解析, AClass 单例 仅在 AServiceProvider 范围内
_serviceProvider = services.BuildServiceProvider();
}
public static void AddPluginCorePlugins(this IServiceCollection services)
{
#region ASP.NET Core
//IPluginManager pluginManager = services.BuildServiceProvider().GetService<IPluginManager>();
IPluginManager pluginManager = _serviceProvider.GetService<IPluginManager>();
// 初始化 PluginCore 相关目录
PluginPathProvider.PluginsRootPath();
// 在程序启动时加载所有 已安装并启用 的插件
// 获取 PluginConfigModel
#region PluginConfigModel
PluginConfigModel pluginConfigModel = PluginConfigModelFactory.Create();
#endregion
// 已启用的插件
#region Assemblies
IList<string> enabledPluginIds = pluginConfigModel.EnabledPlugins;
foreach (var pluginId in enabledPluginIds)
{
pluginManager.LoadPlugin(pluginId);
}
#endregion
//注销内置Controller的注册 by tomny
// 将本 Assembly 内的 Controller 添加
// var ass = Assembly.GetExecutingAssembly();
//IPluginControllerManager pluginControllerManager = services.BuildServiceProvider().GetService<IPluginControllerManager>();
// IPluginControllerManager pluginControllerManager = _serviceProvider.GetService<IPluginControllerManager>();
//pluginControllerManager.AddControllers(ass);
// IWebHostEnvironment
_webHostEnvironment = services.BuildServiceProvider().GetService<IWebHostEnvironment>();
#endregion
}
public static void AddPluginCoreAuthentication(this IServiceCollection services)
{
// fixed: https://github.com/yiyungent/PluginCore/issues/4
// System.InvalidOperationException: No authenticationScheme was specified, and there was no DefaultChallengeScheme found. The default schemes can be set using either AddAuthentication(string defaultScheme) or AddAuthentication(Action<AuthenticationOptions> configureOptions).
#region Authentication
// 没通过 Authentication: 401 Unauthorized
// services.AddAuthentication("PluginCore.Authentication")
// .AddScheme<Authentication.PluginCoreAuthenticationSchemeOptions,
// Authentication.PluginCoreAuthenticationHandler>("PluginCore.Authentication", "PluginCore.Authentication",
// options => { });
// 注意: 不要设置 默认 认证名: Constants.AspNetCoreAuthenticationScheme
// services.AddAuthentication(Constants.AspNetCoreAuthenticationScheme)
// 默认认证名: 默认
services.AddAuthentication()
// 添加一个新的认证方案
.AddScheme<Authentication.PluginCoreAuthenticationSchemeOptions, Authentication.PluginCoreAuthenticationHandler>(
authenticationScheme: Constants.AspNetCoreAuthenticationScheme,
displayName: Constants.AspNetCoreAuthenticationScheme,
options => { });
#endregion
}
public static void AddPluginCoreAuthorization(this IServiceCollection services)
{
#region - [PluginCoreAdminAuthorize]
// Only Once, Not recommend
//services.AddSingleton<IAuthorizationHandler, PluginCoreAdminAuthorizationHandler>();
services.AddAuthorization(options =>
{
// options.AddPolicy("PluginCore.Admin", policy =>
options.AddPolicy(name: Constants.AspNetCoreAuthorizationPolicyName, policy =>
{
// 无法满足 下方任何一项HTTP 403 错误
// 3.需要 检查是否拥有当前请求资源的权限
//policy.Requirements.Add(new PluginCoreAdminRequirement());
policy.AuthenticationSchemes = new string[] {
Constants.AspNetCoreAuthenticationScheme
};
policy.RequireAuthenticatedUser();
policy.RequireClaim(claimType: Constants.AspNetCoreAuthenticationClaimType);
// 必须重启才能使得更改密码生效
string token = AccountManager.CreateToken();
policy.RequireClaim(claimType: Constants.AspNetCoreAuthenticationClaimType, allowedValues: new string[] {
token
});
//policy.RequireAssertion(context =>
//{
// return true;
//});
});
});
#endregion
// AccountManager
services.AddTransient<AccountManager>();
// HttpContext.Current
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
//services.AddHttpContextAccessor();
}
public static void AddPluginCoreStartupPlugin(this IServiceCollection services)
{
//IPluginFinder pluginFinder = services.BuildServiceProvider().GetService<IPluginFinder>();
IPluginFinder pluginFinder = _serviceProvider.GetService<IPluginFinder>();
#region IStartupPlugin
var plugins = pluginFinder.EnablePlugins<IStartupPlugin>()?.OrderBy(m => m.ConfigureServicesOrder)?.ToList();
foreach (var item in plugins)
{
// 调用
Utils.LogUtil.Info(
categoryName: $"{nameof(PluginCoreStartupExtensions)}.{nameof(AddPluginCoreStartupPlugin)}",
message: $"{item.GetType().ToString()}: {nameof(IStartupPlugin)}.{nameof(IStartupPlugin.ConfigureServices)}"
);
item?.ConfigureServices(services);
}
#endregion
}
public static void AddPluginCoreLog(this IServiceCollection services)
{
#region Logger
IServiceScopeFactory serviceScopeFactory = _serviceProvider.GetService<IServiceScopeFactory>();
Utils.LogUtil.Initialize(serviceScopeFactory);
#endregion
}
public static IApplicationBuilder UsePluginCoreStaticFiles(this IApplicationBuilder app)
{
// TODO: 其实由于目前已实现运行时动态新增/删除 HTTP Middleware, 其实可以不用再像下方这么复制插件 wwwroot 目录到 Plugins_wwwroot/{PluginId}
// 而是在运行时配置, 直接指向 `Plugins/{PluginId}/wwwroot`, 而无需复制/删除
// 注意:`UseDefaultFiles`必须在`UseStaticFiles`之前进行调用。因为`DefaultFilesMiddleware`仅仅负责重写Url实际上默认页文件仍然是通过`StaticFilesMiddleware`来提供的。
string pluginwwwrootDir = PluginPathProvider.PluginsWwwRootDir();
#region wwwroot
// 设置目录的默认页
var defaultFilesOptions = new DefaultFilesOptions();
defaultFilesOptions.DefaultFileNames.Clear();
// 指定默认页名称
defaultFilesOptions.DefaultFileNames.Add("index.html");
// 指定请求路径
defaultFilesOptions.RequestPath = "/Plugins";
// 指定默认页所在的目录
defaultFilesOptions.FileProvider = new PhysicalFileProvider(pluginwwwrootDir);
app.UseDefaultFiles(defaultFilesOptions);
#endregion
#region wwwroot
// 由于没办法在运行时, 动态 UseStaticFiles(), 因此不再为每一个插件都 UseStaticFiles(),
// 而是统一在一个文件夹下, 插件启用时, 将插件的wwwroot复制到 Plugins_wwwroot/{PluginId}, 禁用时, 再删除
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
pluginwwwrootDir),
RequestPath = "/Plugins"
});
#endregion
return app;
}
public static IApplicationBuilder UsePluginCoreAppStart(this IApplicationBuilder app)
{
//IPluginFinder pluginFinder = _services.BuildServiceProvider().GetService<IPluginFinder>();
IPluginFinder pluginFinder = _serviceProvider.GetService<IPluginFinder>();
#region AppStart
var plugins = pluginFinder.EnablePluginsFull()?.ToList();
var dependencySorter = new PluginCore.Utils.DependencySorter<IPlugin>();
dependencySorter.AddObjects(plugins.Select(m => m.PluginInstance).ToArray());
foreach (var item in plugins)
{
var dependPlugins = plugins.Where(m => item.PluginInstance.AppStartOrderDependPlugins.Contains(m.PluginId)).Select(m => m.PluginInstance).ToArray();
dependencySorter.SetDependencies(obj: item.PluginInstance, dependsOnObjects: dependPlugins);
}
var sortedPlugins = dependencySorter.Sort();
foreach (var item in sortedPlugins)
{
// 调用
//Utils.LogUtil.PluginBehavior(item, typeof(IPlugin), nameof(IPlugin.AppStart));
Utils.LogUtil.Info(
categoryName: $"{nameof(PluginCoreStartupExtensions)}.{nameof(UsePluginCoreAppStart)}",
message: $"{item.GetType().ToString()}: {nameof(IPlugin)}.{nameof(IPlugin.AppStart)}"
);
item?.AppStart();
}
#endregion
return app;
}
public static IApplicationBuilder UsePluginCoreStartupPlugin(this IApplicationBuilder app)
{
//IPluginFinder pluginFinder = _services.BuildServiceProvider().GetService<IPluginFinder>();
IPluginFinder pluginFinder = _serviceProvider.GetService<IPluginFinder>();
#region IStartupPlugin
var startupPlugins = pluginFinder.EnablePlugins<IStartupPlugin>()?.OrderBy(m => m.ConfigureOrder)?.ToList();
foreach (var item in startupPlugins)
{
// 调用
//Utils.LogUtil.PluginBehavior(item, typeof(IStartupPlugin), nameof(IStartupPlugin.Configure));
Utils.LogUtil.Info(
categoryName: $"{nameof(PluginCoreStartupExtensions)}.{nameof(UsePluginCoreStartupPlugin)}",
message: $"{item.GetType().ToString()}: {nameof(IStartupPlugin)}.{nameof(IStartupPlugin.Configure)}"
);
item?.Configure(app);
}
#endregion
app.UseMiddleware<PluginStartupXMiddleware>();
return app;
}
public static IApplicationBuilder UsePluginCoreLog(this IApplicationBuilder app)
{
#region Log
Config.PluginCoreConfig pluginCoreConfig = Config.PluginCoreConfigFactory.Create();
Utils.LogUtil.Info(categoryName: $"{nameof(PluginCoreStartupExtensions)}.{nameof(UsePluginCoreLog)}", $"{nameof(PluginCore.AspNetCore)}");
Utils.LogUtil.Info(categoryName: $"{nameof(PluginCoreStartupExtensions)}.{nameof(UsePluginCoreLog)}", "Started successfully:");
Utils.LogUtil.Info(categoryName: $"{nameof(PluginCoreStartupExtensions)}.{nameof(UsePluginCoreLog)}", $"Front-end mode: {pluginCoreConfig.FrontendMode}");
Utils.LogUtil.Info(categoryName: $"{nameof(PluginCoreStartupExtensions)}.{nameof(UsePluginCoreLog)}", $"Notice: Updating the front-end mode requires restarting the site");
#endregion
return app;
}
}
}

View File

@ -1,349 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Internal;
namespace PluginCore.AspNetCore.Infrastructure
{
/// <summary>
/// Default implementation for <see cref="IApplicationBuilder"/>.
///
/// https://github.com/dotnet/aspnetcore/blob/main/src/Http/Http/src/Builder/ApplicationBuilder.cs
/// </summary>
public class PluginApplicationBuilder : IApplicationBuilder
{
private const string ServerFeaturesKey = "server.Features";
private const string ApplicationServicesKey = "application.Services";
private readonly List<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
private Action _reachEndAction;
public Action ReachEndAction
{
get
{
return this._reachEndAction;
}
set
{
this._reachEndAction = value;
}
}
public PluginApplicationBuilder()
{
}
/// <summary>
/// Initializes a new instance of <see cref="ApplicationBuilder"/>.
/// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param>
public PluginApplicationBuilder(IServiceProvider serviceProvider, Action reachEndAction)
{
Properties = new Dictionary<string, object?>(StringComparer.Ordinal);
ApplicationServices = serviceProvider;
_reachEndAction = reachEndAction;
}
/// <summary>
/// Initializes a new instance of <see cref="ApplicationBuilder"/>.
/// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> for application services.</param>
/// <param name="server">The server instance that hosts the application.</param>
public PluginApplicationBuilder(IServiceProvider serviceProvider, Action reachEndAction, object server)
: this(serviceProvider, reachEndAction)
{
SetProperty(ServerFeaturesKey, server);
}
private PluginApplicationBuilder(PluginApplicationBuilder builder)
{
Properties = new CopyOnWriteDictionary<string, object?>(builder.Properties, StringComparer.Ordinal);
}
/// <summary>
/// Gets the <see cref="IServiceProvider"/> for application services.
/// </summary>
public IServiceProvider ApplicationServices
{
get
{
//return GetProperty<IServiceProvider>(ApplicationServicesKey)!;
return null;
}
set
{
SetProperty<IServiceProvider>(ApplicationServicesKey, value);
}
}
/// <summary>
/// Gets the <see cref="IFeatureCollection"/> for server features.
/// </summary>
public IFeatureCollection ServerFeatures
{
get
{
// ! null 包容运算符C# 参考)
// https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/null-forgiving
// TODO: 报错
//var result = GetProperty<IFeatureCollection>(ServerFeaturesKey)!;
IFeatureCollection rtn = null;
return rtn;
}
}
/// <summary>
/// Gets a set of properties for <see cref="ApplicationBuilder"/>.
/// </summary>
public IDictionary<string, object?> Properties { get; }
//private T? GetProperty<T>(string key)
//{
// return Properties.TryGetValue(key, out var value) ? (T?)value : default(T);
//}
private void SetProperty<T>(string key, T value)
{
Properties[key] = value;
}
/// <summary>
/// Adds the middleware to the application request pipeline.
/// </summary>
/// <param name="middleware">The middleware.</param>
/// <returns>An instance of <see cref="IApplicationBuilder"/> after the operation has completed.</returns>
public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
{
_components.Add(middleware);
return this;
}
/// <summary>
/// Creates a copy of this application builder.
/// <para>
/// The created clone has the same properties as the current instance, but does not copy
/// the request pipeline.
/// </para>
/// </summary>
/// <returns>The cloned instance.</returns>
public IApplicationBuilder New()
{
return new PluginApplicationBuilder(this);
}
/// <summary>
/// Produces a <see cref="RequestDelegate"/> that executes added middlewares.
/// </summary>
/// <returns>The <see cref="RequestDelegate"/>.</returns>
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
#region Old
// If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
// This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
//var endpoint = context.GetEndpoint();
//var endpointRequestDelegate = endpoint?.RequestDelegate;
//if (endpointRequestDelegate != null)
//{
// var message =
// $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
// $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
// $"routing.";
// throw new InvalidOperationException(message);
//}
#endregion
this._reachEndAction();
//context.Response.StatusCode = StatusCodes.Status404NotFound;
return Task.CompletedTask;
};
for (var c = _components.Count - 1; c >= 0; c--)
{
app = _components[c](app);
}
return app;
}
}
/// <summary>
/// https://github.com/dotnet/aspnetcore/blob/8b30d862de6c9146f466061d51aa3f1414ee2337/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
internal class CopyOnWriteDictionary<TKey, TValue> : IDictionary<TKey, TValue> where TKey : notnull
{
private readonly IDictionary<TKey, TValue> _sourceDictionary;
private readonly IEqualityComparer<TKey> _comparer;
private IDictionary<TKey, TValue>? _innerDictionary;
public CopyOnWriteDictionary(
IDictionary<TKey, TValue> sourceDictionary,
IEqualityComparer<TKey> comparer)
{
if (sourceDictionary == null)
{
throw new ArgumentNullException(nameof(sourceDictionary));
}
if (comparer == null)
{
throw new ArgumentNullException(nameof(comparer));
}
_sourceDictionary = sourceDictionary;
_comparer = comparer;
}
private IDictionary<TKey, TValue> ReadDictionary
{
get
{
return _innerDictionary ?? _sourceDictionary;
}
}
private IDictionary<TKey, TValue> WriteDictionary
{
get
{
if (_innerDictionary == null)
{
_innerDictionary = new Dictionary<TKey, TValue>(_sourceDictionary,
_comparer);
}
return _innerDictionary;
}
}
public virtual ICollection<TKey> Keys
{
get
{
return ReadDictionary.Keys;
}
}
public virtual ICollection<TValue> Values
{
get
{
return ReadDictionary.Values;
}
}
public virtual int Count
{
get
{
return ReadDictionary.Count;
}
}
public virtual bool IsReadOnly
{
get
{
return false;
}
}
public virtual TValue this[TKey key]
{
get
{
return ReadDictionary[key];
}
set
{
WriteDictionary[key] = value;
}
}
public virtual bool ContainsKey(TKey key)
{
return ReadDictionary.ContainsKey(key);
}
public virtual void Add(TKey key, TValue value)
{
WriteDictionary.Add(key, value);
}
public virtual bool Remove(TKey key)
{
return WriteDictionary.Remove(key);
}
public virtual bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
{
return ReadDictionary.TryGetValue(key, out value);
}
public virtual void Add(KeyValuePair<TKey, TValue> item)
{
WriteDictionary.Add(item);
}
public virtual void Clear()
{
WriteDictionary.Clear();
}
public virtual bool Contains(KeyValuePair<TKey, TValue> item)
{
return ReadDictionary.Contains(item);
}
public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
ReadDictionary.CopyTo(array, arrayIndex);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return WriteDictionary.Remove(item);
}
public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return ReadDictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -1,61 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using PluginCore.AspNetCore.Extensions;
[assembly: HostingStartup(typeof(PluginCore.AspNetCore.Infrastructure.PluginCoreHostingStartup))]
namespace PluginCore.AspNetCore.Infrastructure
{
/// <summary>
/// https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/platform-specific-configuration?view=aspnetcore-5.0
/// </summary>
public class PluginCoreHostingStartup : IHostingStartup
{
public PluginCoreHostingStartup()
{
}
public void Configure(IWebHostBuilder builder)
{
//builder.ConfigureAppConfiguration(config =>
//{
//});
// 注意: 无论是通过 Program.cs 中 webBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "PluginCore");
// 还是 通过环境变量 指定承载启动程序集, 都是先执行 外部的承载启动程序集, 再执行主程序的 Startup.cs, 因此在这时, 有些 service 还没有注册
// TODO: 不知道, 重复 Add, Use 会导致什么, 没有做防止重复
builder.ConfigureServices(services =>
{
// fixed: https://github.com/yiyungent/PluginCore/issues/1
// System.InvalidOperationException: 'Unable to resolve service for type 'Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager'
// TODO: 不确定, 这样是否可行, 事实上之后主程序还会 Add 一次, 不知道是否会导致存在多个 实例
// 失败: 不是一个实例, 导致无法改变 Controller
//Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager applicationPartManager =
// new Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager();
//services.AddSingleton<Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager>(applicationPartManager);
services.AddPluginCore();
});
builder.Configure(app =>
{
app.UsePluginCore();
});
}
}
}

View File

@ -1,24 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Text;
namespace PluginCore.AspNetCore.Interfaces
{
public interface IPluginApplicationBuilderManager
{
void ReBuild();
RequestDelegate GetBuildResult();
}
}

View File

@ -1,24 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace PluginCore.AspNetCore.Interfaces
{
public interface IPluginControllerManager
{
void AddControllers(Assembly assembly);
void RemoveControllers(string pluginId);
}
}

View File

@ -1,46 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using PluginCore.IPlugins;
namespace PluginCore.AspNetCore.Middlewares
{
public class LanguageMiddleware
{
private readonly RequestDelegate _next;
public LanguageMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
// 从 Cookie 中获取语言标识
string language = httpContext.Request.Cookies[Constants.AspNetCoreLanguageCookieName];
// 存储当前语言到 HttpContext.Items
httpContext.Items[Constants.AspNetCoreLanguageKey] = language;
// 调用下一个中间件
await _next(httpContext);
}
}
public static class LanguageMiddlewareExtensions
{
public static IApplicationBuilder UsePluginCoreLanguageMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LanguageMiddleware>();
}
}
}

View File

@ -1,149 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using PluginCore.Interfaces;
namespace PluginCore.AspNetCore.Middlewares
{
/// <summary>
/// TODO: 未测试
/// </summary>
public class PluginContentFilterMiddleware
{
private readonly RequestDelegate _next;
private readonly IPluginFinder _pluginFinder;
public PluginContentFilterMiddleware(
RequestDelegate next,
IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory,
IPluginFinder pluginFinder)
{
_next = next;
_pluginFinder = pluginFinder;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var httpMethod = httpContext.Request.Method;
var path = httpContext.Request.Path.Value;
// 在请求下一个 middleware 前过滤
await RequestBodyFilter(httpContext);
// Call the next delegate/middleware in the pipeline
await _next(httpContext);
if (httpMethod == "GET" && path.EndsWith(".css") || path.EndsWith(".js") || path.EndsWith(".html"))
{
// middleware 回退时 过滤
await ResponseBodyFilter(httpContext);
}
}
private async Task RequestBodyFilter(HttpContext httpContext)
{
string content = string.Empty;
using (var memoryStream = new MemoryStream())
{
// Response.Body
await httpContext.Request.Body.CopyToAsync(memoryStream);
long pos = httpContext.Request.Body.Position;
using (var reader = new StreamReader(memoryStream, Encoding.UTF8))
{
content = await reader.ReadToEndAsync();
}
}
var plugins = this._pluginFinder.EnablePlugins<PluginCore.IPlugins.IContentFilterPlugin>().ToList();
foreach (var item in plugins)
{
// 调用
content = await item?.RequestBodyFilter(httpContext.Request.Path.Value, content);
}
// 更新 Request.Body
#region 1
//var requestStream = new MemoryStream();
//using (StreamWriter writer = new StreamWriter(requestStream, Encoding.UTF8))
//{
// await writer.WriteAsync(content);
//}
//httpContext.Request.Body = requestStream;
#endregion
#region 2
httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
byte[] buffer = Encoding.UTF8.GetBytes(content);
await httpContext.Request.Body.WriteAsync(buffer, 0, buffer.Length);
#endregion
}
private async Task ResponseBodyFilter(HttpContext httpContext)
{
string content = string.Empty;
using (var memoryStream = new MemoryStream())
{
// Response.Body
await httpContext.Response.Body.CopyToAsync(memoryStream);
long pos = httpContext.Response.Body.Position;
using (var reader = new StreamReader(memoryStream, Encoding.UTF8))
{
content = await reader.ReadToEndAsync();
}
}
var plugins = this._pluginFinder.EnablePlugins<PluginCore.IPlugins.IContentFilterPlugin>().ToList();
foreach (var item in plugins)
{
// 调用
content = await item?.ReponseBodyFilter(httpContext.Request.Path.Value, content);
}
// 更新 ResponseBody
#region 1
//var responseStream = new MemoryStream();
//using (StreamWriter writer = new StreamWriter(responseStream, Encoding.UTF8))
//{
// await writer.WriteAsync(content);
//}
//httpContext.Response.Body = responseStream;
#endregion
#region 2
httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
byte[] buffer = Encoding.UTF8.GetBytes(content);
await httpContext.Response.Body.WriteAsync(buffer, 0, buffer.Length);
#endregion
}
}
}

View File

@ -1,71 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using PluginCore.Interfaces;
namespace PluginCore.AspNetCore.Middlewares
{
/// <summary>
/// 一定在 PluginCore 添加的中间件中 最后一个
/// </summary>
public class PluginHttpEndFilterMiddleware
{
private readonly RequestDelegate _next;
private readonly IPluginFinder _pluginFinder;
public PluginHttpEndFilterMiddleware(
RequestDelegate next,
IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory,
IPluginFinder pluginFinder)
{
_next = next;
_pluginFinder = pluginFinder;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var httpMethod = httpContext.Request.Method;
var path = httpContext.Request.Path.Value;
// 在请求下一个 middleware 前过滤
// Call the next delegate/middleware in the pipeline
await _next(httpContext);
// middleware 回退时 过滤
await Filter(httpContext);
}
private async Task Filter(HttpContext httpContext)
{
var plugins = this._pluginFinder.EnablePlugins<PluginCore.IPlugins.IHttpFilterPlugin>().ToList();
foreach (var item in plugins)
{
// 调用
await item?.HttpEndFilter(httpContext);
}
}
}
}

View File

@ -1,71 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using PluginCore.Interfaces;
namespace PluginCore.AspNetCore.Middlewares
{
/// <summary>
/// 一定在 PluginCore 添加的中间件中 第一个
/// </summary>
public class PluginHttpStartFilterMiddleware
{
private readonly RequestDelegate _next;
private readonly IPluginFinder _pluginFinder;
public PluginHttpStartFilterMiddleware(
RequestDelegate next,
IWebHostEnvironment hostingEnv,
ILoggerFactory loggerFactory,
IPluginFinder pluginFinder)
{
_next = next;
_pluginFinder = pluginFinder;
}
public async Task InvokeAsync(HttpContext httpContext)
{
var httpMethod = httpContext.Request.Method;
var path = httpContext.Request.Path.Value;
// 在请求下一个 middleware 前过滤
await Filter(httpContext);
// Call the next delegate/middleware in the pipeline
await _next(httpContext);
// middleware 回退时 过滤
}
private async Task Filter(HttpContext httpContext)
{
var plugins = this._pluginFinder.EnablePlugins<PluginCore.IPlugins.IHttpFilterPlugin>().ToList();
foreach (var item in plugins)
{
// 调用
await item?.HttpStartFilter(httpContext);
}
}
}
}

View File

@ -1,71 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using PluginCore.AspNetCore.Interfaces;
using PluginCore.Infrastructure;
namespace PluginCore.AspNetCore.Middlewares
{
public class PluginStartupXMiddleware
{
private readonly RequestDelegate _next;
public PluginStartupXMiddleware(RequestDelegate next)
{
_next = next;
}
public static Action ReachedEndAction { get; set; } = () => { _isReachedEnd = true; };
private static bool _isReachedEnd;
public async Task InvokeAsync(HttpContext httpContext, IPluginApplicationBuilderManager pluginApplicationBuilderManager)
{
//bool isReachedEnd = false;
_isReachedEnd = false;
try
{
RequestDelegate requestDelegate = pluginApplicationBuilderManager.GetBuildResult();
await requestDelegate(httpContext);
}
catch (Exception ex)
{
// InvalidOperationException: The request reached the end of the pipeline without executing the endpoint: 'AspNetCore3_1.Controllers.WeatherForecastController.Get (AspNetCore3_1)'. Please register the EndpointMiddleware using 'IApplicationBuilder.UseEndpoints(...)' if using routing.
Utils.LogUtil.Error<PluginStartupXMiddleware>(ex, ex.Message);
if (ex.InnerException != null)
{
Utils.LogUtil.Error<PluginStartupXMiddleware>(ex.InnerException, ex.InnerException.Message);
}
}
if (_isReachedEnd)
{
// Call the next delegate/middleware in the pipeline
await _next(httpContext);
}
else
{
// 没有抵达 End, 说明在插件的 middleware 中已堵塞, 准备返回 响应
}
}
}
}

View File

@ -1,107 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<PackageId>PluginCore.AspNetCore</PackageId>
<Version>1.4.3</Version>
<Company>yiyun</Company>
<Authors>yiyun</Authors>
<Description>ASP.NET Core lightweight plugin framework</Description>
<Copyright>Copyright (c) 2021-present yiyun</Copyright>
<RepositoryUrl>https://github.com/yiyungent/PluginCore</RepositoryUrl>
<PackageLicenseUrl>https://github.com/yiyungent/PluginCore/blob/main/LICENSE</PackageLicenseUrl>
<PackageTags>PluginCore PluginCore.AspNetCore</PackageTags>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<!-- 方便开发debug,与发布到nuget -->
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="PluginCore" Version="2.2.5" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\PluginCore\PluginCore.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="PluginCore.IPlugins.AspNetCore" Version="0.1.1" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\PluginCore.IPlugins.AspNetCore\PluginCore.IPlugins.AspNetCore.csproj" />
</ItemGroup>
<!--<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="5.0.2" />
</ItemGroup>-->
<!-- 生成注释xml -->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp3.1|AnyCPU'">
<DocumentationFile>bin\Release\netcoreapp3.1\PluginCore.AspNetCore.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net5.0|AnyCPU'">
<DocumentationFile>bin\Release\net5.0\PluginCore.AspNetCore.xml</DocumentationFile>
</PropertyGroup>
<!-- 0.4.0 : 支持嵌入式 前端 (打包进dll) -->
<ItemGroup>
<EmbeddedResource Include="node_modules/plugincore-admin-frontend/dist/**/**/*" />
</ItemGroup>
<ItemGroup>
<Compile Remove="node_modules\**" />
<EmbeddedResource Remove="node_modules\**" />
<None Remove="node_modules\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Controllers忽略\AppCenterController.cs" />
<Compile Remove="Controllers忽略\DebugController.cs" />
<Compile Remove="Controllers忽略\HomeController.cs" />
<Compile Remove="Controllers忽略\PluginsController.cs" />
<Compile Remove="Controllers忽略\PluginWidgetController.cs" />
<Compile Remove="Controllers忽略\UserController.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="package-lock.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers忽略\" />
</ItemGroup>
<!-- 将 PluginCoreAdmin\*\* 与 readme.txt 复制到build后文件夹且加入 nupkg -->
<!-- 从 0.2.0 开始, 支持远程前端,本地前端不再必要 -->
<!--<ItemGroup>
<Content Include="PluginCoreAdmin\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
<Content Include="PluginCoreAdmin\*\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
<Content Include="PluginCoreAdmin\*\*\*">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
<Content Include="readme.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>-->
<!-- Use Visual Studio npm package if it is installed. -->
<PropertyGroup Condition="Exists('$(VsInstallRoot)\Web\External\npm.cmd')">
<Path>$(Path)$(VsInstallRoot)\Web\External\;</Path>
</PropertyGroup>
<Target Name="NpmInstall" BeforeTargets="Build">
<Exec Command="npm install" EnvironmentVariables="PATH=$(Path.Replace(';', '%3B'))" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js/npm is required to build this project. To continue, please install Node.js from https://nodejs.org/ or Visual Studio Installer, and then restart your command prompt or IDE." />
</Target>
</Project>

View File

@ -1,23 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
namespace PluginCore.AspNetCore.RequestModel.User
{
public class LoginRequestModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
}

View File

@ -1,23 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
namespace PluginCore.AspNetCore.RequestModel.User
{
public class UpdateRequestModel
{
public string UserName { get; set; }
public string Password { get; set; }
}
}

View File

@ -1,21 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace PluginCore.AspNetCore.ResponseModel
{
public class BaseResponseModel
{
public int Code { get; set; }
public string Message { get; set; }
public object Data { get; set; }
}
}

View File

@ -1,82 +0,0 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
---
{% if version %}\
{% if previous.version %}\
## [{{ version | trim_start_matches(pat="v") }}]($REPO/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% endif %}\
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
- **({{commit.scope}})**{% if commit.breaking %} [**breaking**]{% endif %} \
{{ commit.message }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {{ commit.author.name }}
{%- endfor -%}
{% raw %}\n{% endraw %}\
{%- for commit in commits %}
{%- if commit.scope -%}
{% else -%}
- {% if commit.breaking %} [**breaking**]{% endif %}\
{{ commit.message }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {{ commit.author.name }}
{% endif -%}
{% endfor -%}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
{ pattern = '\$REPO', replace = "https://github.com/yiyungent/PluginCore" }, # replace repository URL
]
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^doc", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactoring" },
{ message = "^style", group = "Style" },
{ message = "^revert", group = "Revert" },
{ message = "^test", group = "Tests" },
{ message = "^chore\\(version\\):", skip = true },
{ message = "^chore", group = "Miscellaneous Chores" },
{ body = ".*security", group = "Security" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

View File

@ -1,81 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.AspNetCore.Interfaces;
using PluginCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using PluginCore.lmplements;
using PluginCore.Infrastructure;
namespace PluginCore.AspNetCore.lmplements
{
/// <summary>
/// 一个插件的所有dll由 一个 <see cref="IPluginContext"/> 管理
/// <see cref="PluginContextManager"/> 记录管理了所有 插件的<see cref="IPluginContext"/>
/// <see cref="AspNetCorePluginManager"/> 是对 <see cref="PluginContextManager"/>的封装, 使其更好管理插件加载释放的行为
/// </summary>
public class AspNetCorePluginManager : IPluginManager
{
private readonly IPluginControllerManager _pluginControllerManager;
public IPluginContextManager PluginContextManager { get; set; }
public IPluginContextPack PluginContextPack { get; set; }
public AspNetCorePluginManager(IPluginContextManager pluginContextManager, IPluginContextPack pluginContextPack, IPluginControllerManager pluginControllerManager)
{
this.PluginContextManager = pluginContextManager;
this.PluginContextPack = pluginContextPack;
_pluginControllerManager = pluginControllerManager;
}
/// <summary>
/// 加载插件程序集
/// </summary>
/// <param name="pluginId"></param>
public void LoadPlugin(string pluginId)
{
IPluginContext context = this.PluginContextPack.Pack(pluginId);
Assembly pluginMainAssembly = context.LoadFromAssemblyName(new AssemblyName(pluginId));
//注销内置Controller的注册 by tomny
// 加载其中的控制器
//_pluginControllerManager.AddControllers(pluginMainAssembly);
// 这个插件加载上下文 放入 集合中
this.PluginContextManager.Add(pluginId, context);
}
public void UnloadPlugin(string pluginId)
{
this.PluginContextManager.Remove(pluginId);
// 移除其中的控制器
_pluginControllerManager.RemoveControllers(pluginId);
}
/// <summary>
/// 加载插件程序集
/// </summary>
/// <param name="pluginId"></param>
public Assembly GetPluginAssembly(string pluginId)
{
IPluginContext context = this.PluginContextPack.Pack(pluginId);
Assembly pluginMainAssembly = context.LoadFromAssemblyName(new AssemblyName(pluginId));
return pluginMainAssembly;
}
}
}

View File

@ -1,83 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using PluginCore.AspNetCore.Interfaces;
using PluginCore.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using System.Text;
using PluginCore.lmplements;
using PluginCore.Infrastructure;
namespace PluginCore.AspNetCore.lmplements
{
// ********* 虽然 看上去和 AspNetCorePluginManager 一样, 但特别保留, 防止需要不一样的处理, 后续更新维护方便
/// <summary>
/// 一个插件的所有dll由 一个 <see cref="IPluginContext"/> 管理
/// <see cref="PluginContextManager"/> 记录管理了所有 插件的<see cref="CollectibleAssemblyLoadContext"/>
/// <see cref="AspNetCorePluginManagerV1"/> 是对 <see cref="PluginContextManager"/>的封装, 使其更好管理插件加载释放的行为
/// </summary>
public class AspNetCorePluginManagerV1 : IPluginManager
{
private readonly IPluginControllerManager _pluginControllerManager;
public IPluginContextManager PluginContextManager { get; set; }
public IPluginContextPack PluginContextPack { get; set; }
public AspNetCorePluginManagerV1(IPluginContextManager pluginContextManager, IPluginContextPack pluginContextPack, IPluginControllerManager pluginControllerManager)
{
this.PluginContextManager = pluginContextManager;
this.PluginContextPack = pluginContextPack;
_pluginControllerManager = pluginControllerManager;
}
/// <summary>
/// 加载插件程序集
/// </summary>
/// <param name="pluginId"></param>
public void LoadPlugin(string pluginId)
{
// 此插件的 加载上下文
IPluginContext context = this.PluginContextPack.Pack(pluginId);
// TODO: 注意: 未测试: 不清除 对于 旧版加载 dll 方式, 再结合 LoadFromAssemblyName 是否有效
Assembly pluginMainAssembly = context.LoadFromAssemblyName(new AssemblyName(pluginId));
//注销内置Controller的注册 by tomny
// 加载其中的控制器
//_pluginControllerManager.AddControllers(pluginMainAssembly);
// 这个插件加载上下文 放入 集合中
this.PluginContextManager.Add(pluginId, context);
}
public void UnloadPlugin(string pluginId)
{
this.PluginContextManager.Remove(pluginId);
// 移除其中的控制器
_pluginControllerManager.RemoveControllers(pluginId);
}
public Assembly GetPluginAssembly(string pluginId)
{
IPluginContext context = this.PluginContextPack.Pack(pluginId);
Assembly pluginMainAssembly = context.LoadFromAssemblyName(new AssemblyName(pluginId));
return pluginMainAssembly;
}
}
}

View File

@ -1,39 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Primitives;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace PluginCore.AspNetCore.lmplements
{
/// <summary>
/// 目前采用的第一种方案
/// 方案一: https://www.codetd.com/article/461093
/// 方案二: https://www.cnblogs.com/artech/p/dynamic-controllers.html
/// </summary>
public class PluginActionDescriptorChangeProvider : IActionDescriptorChangeProvider
{
public static PluginActionDescriptorChangeProvider Instance { get; } = new PluginActionDescriptorChangeProvider();
public CancellationTokenSource TokenSource { get; private set; }
public bool HasChanged { get; set; }
public IChangeToken GetChangeToken()
{
TokenSource = new CancellationTokenSource();
return new CancellationChangeToken(TokenSource.Token);
}
}
}

View File

@ -1,77 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using PluginCore.AspNetCore.Interfaces;
using PluginCore.Interfaces;
using PluginCore.IPlugins;
using PluginCore.AspNetCore.Middlewares;
using PluginCore.AspNetCore.Infrastructure;
namespace PluginCore.AspNetCore.lmplements
{
public class PluginApplicationBuilderManager : PluginApplicationBuilderManager<PluginApplicationBuilder>
{
public PluginApplicationBuilderManager(IPluginFinder pluginFinder) : base(pluginFinder)
{
}
}
public class PluginApplicationBuilderManager<TPluginApplicationBuilder> : IPluginApplicationBuilderManager
where TPluginApplicationBuilder : PluginApplicationBuilder, new()
{
private readonly IPluginFinder _pluginFinder;
public PluginApplicationBuilderManager(IPluginFinder pluginFinder)
{
_pluginFinder = pluginFinder;
}
public static RequestDelegate RequestDelegateResult { get; set; }
/// <summary>
/// 插件 启用, 禁用 时: 重新 Build
/// </summary>
public void ReBuild()
{
TPluginApplicationBuilder applicationBuilder = new TPluginApplicationBuilder();
applicationBuilder.ReachEndAction = PluginStartupXMiddleware.ReachedEndAction;
var plugins = this._pluginFinder.EnablePlugins<IStartupXPlugin>()?.OrderBy(m => m.ConfigureOrder)?.ToList();
foreach (var item in plugins)
{
// 调用
Utils.LogUtil.Info<PluginApplicationBuilderManager>($"{item.GetType().ToString()} {nameof(IStartupXPlugin)}.{nameof(IStartupXPlugin.Configure)}");
item.Configure(applicationBuilder);
}
RequestDelegateResult = applicationBuilder.Build();
}
public RequestDelegate GetBuildResult()
{
if (RequestDelegateResult == null)
{
ReBuild();
}
return RequestDelegateResult;
}
}
}

View File

@ -1,65 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using PluginCore.AspNetCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace PluginCore.AspNetCore.lmplements
{
public class PluginControllerManager : IPluginControllerManager
{
private readonly ApplicationPartManager _applicationPartManager;
public PluginControllerManager(ApplicationPartManager applicationPartManager)
{
_applicationPartManager = applicationPartManager;
}
/// <summary>
/// 从指定<see cref="Assembly"/> 中获取 Controller, 并添加进来
/// </summary>
/// <param name="assembly"></param>
public void AddControllers(Assembly assembly)
{
AssemblyPart assemblyPart = new AssemblyPart(assembly);
_applicationPartManager.ApplicationParts.Add(assemblyPart);
ResetControllActions();
}
public void RemoveControllers(string pluginId)
{
ApplicationPart last = _applicationPartManager.ApplicationParts.First(m => m.Name == pluginId);
_applicationPartManager.ApplicationParts.Remove(last);
ResetControllActions();
}
/// <summary>
/// 通知应用(主程序)Controller.Action 已发生变化
/// </summary>
private void ResetControllActions()
{
PluginActionDescriptorChangeProvider.Instance.HasChanged = true;
// TokenSource 为 null
// 注意: 在程序刚启动时, 未抵达 Controller 时不会触发 IActionDescriptorChangeProvider.GetChangeToken(), 也就会导致 TokenSource 为 null, 在启动时同时在启动时插件Controller.Action和主程序一起被添加此时无需通知改变
if (PluginActionDescriptorChangeProvider.Instance.TokenSource != null)
{
// 只有在插件列表控制启用,禁用时才需通知改变
PluginActionDescriptorChangeProvider.Instance.TokenSource.Cancel();
}
}
}
}

View File

@ -1 +0,0 @@
dotnet pack -c Release

View File

@ -1,307 +0,0 @@
{
"name": "PluginCore",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "PluginCore",
"version": "1.0.0",
"dependencies": {
"plugincore-admin-frontend": "0.3.2"
}
},
"node_modules/ansi-styles": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-1.0.0.tgz",
"integrity": "sha512-3iF4FIKdxaVYT3JqQuY3Wat/T2t7TRbbQ94Fu50ZUCbLy4TFbTzr90NOHQodQkNqmeEGCw8WbeP78WNi6SKYUA==",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/async-validator": {
"version": "1.8.5",
"resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-1.8.5.tgz",
"integrity": "sha512-tXBM+1m056MAX0E8TL2iCjg8WvSyXu0Zc8LNtYqrVeyoL3+esHRZ4SieE9fKQyyU09uONjnMEjrNBMqT0mbvmA==",
"dependencies": {
"babel-runtime": "6.x"
}
},
"node_modules/axios": {
"version": "0.18.1",
"resolved": "https://registry.npmmirror.com/axios/-/axios-0.18.1.tgz",
"integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==",
"deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410",
"dependencies": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
}
},
"node_modules/babel-helper-vue-jsx-merge-props": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz",
"integrity": "sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg=="
},
"node_modules/babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==",
"dependencies": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
}
},
"node_modules/babel-runtime/node_modules/core-js": {
"version": "2.6.12",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==",
"deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
"hasInstallScript": true
},
"node_modules/chalk": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-0.4.0.tgz",
"integrity": "sha512-sQfYDlfv2DGVtjdoQqxS0cEZDroyG8h6TamA6rvxwlrU5BaSLDx9xhatBYl2pxZ7gmpNaPFVwBtdGdu5rQ+tYQ==",
"dependencies": {
"ansi-styles": "~1.0.0",
"has-color": "~0.1.0",
"strip-ansi": "~0.1.0"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/codemirror": {
"version": "5.65.2",
"resolved": "https://registry.npmmirror.com/codemirror/-/codemirror-5.65.2.tgz",
"integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA=="
},
"node_modules/core-js": {
"version": "3.6.5",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.6.5.tgz",
"integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==",
"deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
"hasInstallScript": true
},
"node_modules/debug": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/deepmerge": {
"version": "1.5.2",
"resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz",
"integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/element-ui": {
"version": "2.13.2",
"resolved": "https://registry.npmmirror.com/element-ui/-/element-ui-2.13.2.tgz",
"integrity": "sha512-r761DRPssMPKDiJZWFlG+4e4vr0cRG/atKr3Eqr8Xi0tQMNbtmYU1QXvFnKiFPFFGkgJ6zS6ASkG+sellcoHlQ==",
"dependencies": {
"async-validator": "~1.8.1",
"babel-helper-vue-jsx-merge-props": "^2.0.0",
"deepmerge": "^1.2.0",
"normalize-wheel": "^1.0.1",
"resize-observer-polyfill": "^1.5.0",
"throttle-debounce": "^1.0.1"
},
"peerDependencies": {
"vue": "^2.5.17"
}
},
"node_modules/follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"dependencies": {
"debug": "=3.1.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/has-color": {
"version": "0.1.7",
"resolved": "https://registry.npmmirror.com/has-color/-/has-color-0.1.7.tgz",
"integrity": "sha512-kaNz5OTAYYmt646Hkqw50/qyxP2vFnTVu5AQ1Zmk22Kk5+4Qx6BpO8+u7IKsML5fOsFk0ZT0AcCJNYwcvaLBvw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-buffer": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/is-buffer/-/is-buffer-2.0.5.tgz",
"integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
"engines": {
"node": ">=4"
}
},
"node_modules/js-cookie": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.0.tgz",
"integrity": "sha512-7YAJP/LPE/MhDjHIdfIiT665HUSumCwPN2hAmO6OJZ8V3o1mtz2HeQ8BKetEjkh+3nqGxYaq1vPMViUR8kaOXw=="
},
"node_modules/jsonlint": {
"version": "1.6.3",
"resolved": "https://registry.npmmirror.com/jsonlint/-/jsonlint-1.6.3.tgz",
"integrity": "sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==",
"dependencies": {
"JSV": "^4.0.x",
"nomnom": "^1.5.x"
},
"bin": {
"jsonlint": "lib/cli.js"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/JSV": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/JSV/-/JSV-4.0.2.tgz",
"integrity": "sha512-ZJ6wx9xaKJ3yFUhq5/sk82PJMuUyLk277I8mQeyDgCTjGdjWJIvPfaU5LIXaMuaN2UO1X3kZH4+lgphublZUHw==",
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/nomnom": {
"version": "1.8.1",
"resolved": "https://registry.npmmirror.com/nomnom/-/nomnom-1.8.1.tgz",
"integrity": "sha512-5s0JxqhDx9/rksG2BTMVN1enjWSvPidpoSgViZU4ZXULyTe+7jxcCRLB6f42Z0l1xYJpleCBtSyY6Lwg3uu5CQ==",
"deprecated": "Package no longer supported. Contact support@npmjs.com for more info.",
"dependencies": {
"chalk": "~0.4.0",
"underscore": "~1.6.0"
}
},
"node_modules/normalize-wheel": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/normalize-wheel/-/normalize-wheel-1.0.1.tgz",
"integrity": "sha512-1OnlAPZ3zgrk8B91HyRj+eVv+kS5u+Z0SCsak6Xil/kmgEia50ga7zfkumayonZrImffAxPU/5WcyGhzetHNPA=="
},
"node_modules/normalize.css": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/normalize.css/-/normalize.css-7.0.0.tgz",
"integrity": "sha512-LYaFZxj2Q1Q9e1VJ0f6laG46Rt5s9URhKyckNaA2vZnL/0gwQHWhM7ALQkp3WBQKM5sXRLQ5Ehrfkp+E/ZiCRg=="
},
"node_modules/nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmmirror.com/nprogress/-/nprogress-0.2.0.tgz",
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA=="
},
"node_modules/path-to-regexp": {
"version": "2.4.0",
"resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz",
"integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w=="
},
"node_modules/plugincore-admin-frontend": {
"version": "0.3.2",
"resolved": "https://registry.npmmirror.com/plugincore-admin-frontend/-/plugincore-admin-frontend-0.3.2.tgz",
"integrity": "sha512-w8t6J+92Ns/gan/3uLW9Uns6vbAEkOzYBzueCJY5b7i+F7wyvmh0TcOHQscm6VmJQW+d73eaTk4psIcUcHbOng==",
"dependencies": {
"axios": "0.18.1",
"codemirror": "^5.57.0",
"core-js": "3.6.5",
"element-ui": "2.13.2",
"js-cookie": "2.2.0",
"jsonlint": "^1.6.3",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"script-loader": "^0.7.2",
"vue": "2.6.10",
"vue-i18n": "^8.26.7",
"vue-meditor": "^2.1.1",
"vue-router": "3.0.6",
"vuex": "3.1.0"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
}
},
"node_modules/raw-loader": {
"version": "0.5.1",
"resolved": "https://registry.npmmirror.com/raw-loader/-/raw-loader-0.5.1.tgz",
"integrity": "sha512-sf7oGoLuaYAScB4VGr0tzetsYlS8EJH6qnTCfQ/WVEa89hALQ4RQfCKt5xCyPQKPDUbVUAIP1QsxAwfAjlDp7Q=="
},
"node_modules/regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
},
"node_modules/script-loader": {
"version": "0.7.2",
"resolved": "https://registry.npmmirror.com/script-loader/-/script-loader-0.7.2.tgz",
"integrity": "sha512-UMNLEvgOAQuzK8ji8qIscM3GIrRCWN6MmMXGD4SD5l6cSycgGsCo0tX5xRnfQcoghqct0tjHjcykgI1PyBE2aA==",
"dependencies": {
"raw-loader": "~0.5.1"
}
},
"node_modules/strip-ansi": {
"version": "0.1.1",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-0.1.1.tgz",
"integrity": "sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==",
"bin": {
"strip-ansi": "cli.js"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/throttle-debounce": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-1.1.0.tgz",
"integrity": "sha512-XH8UiPCQcWNuk2LYePibW/4qL97+ZQ1AN3FNXwZRBNPPowo/NRU5fAlDCSNBJIYCKbioZfuYtMhG4quqoJhVzg==",
"engines": {
"node": ">=4"
}
},
"node_modules/underscore": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/underscore/-/underscore-1.6.0.tgz",
"integrity": "sha512-z4o1fvKUojIWh9XuaVLUDdf86RQiq13AC1dmHbTpoyuu+bquHms76v16CjycCbec87J7z0k//SiQVk0sMdFmpQ=="
},
"node_modules/vue": {
"version": "2.6.10",
"resolved": "https://registry.npmmirror.com/vue/-/vue-2.6.10.tgz",
"integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ=="
},
"node_modules/vue-i18n": {
"version": "8.27.1",
"resolved": "https://registry.npmmirror.com/vue-i18n/-/vue-i18n-8.27.1.tgz",
"integrity": "sha512-lWrGm4F25qReJ7XxSnFVb2h3PfW54ldnM4C+YLBGGJ75+Myt/kj4hHSTKqsyDLamvNYpvINMicSOdW+7yuqgIQ=="
},
"node_modules/vue-meditor": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/vue-meditor/-/vue-meditor-2.1.1.tgz",
"integrity": "sha512-xvLqqvS4LzEJ7TqW2mDSzvueMey/WM92o0jfmqZWBTks1/8EAmn0ehqZDlcq/W+1goQpUmWE4m3yJjzQnoZhQQ=="
},
"node_modules/vue-router": {
"version": "3.0.6",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-3.0.6.tgz",
"integrity": "sha512-Ox0ciFLswtSGRTHYhGvx2L44sVbTPNS+uD2kRISuo8B39Y79rOo0Kw0hzupTmiVtftQYCZl87mwldhh2L9Aquw=="
},
"node_modules/vuex": {
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/vuex/-/vuex-3.1.0.tgz",
"integrity": "sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg=="
}
}
}

View File

@ -1,8 +0,0 @@
{
"name": "PluginCore",
"version": "1.0.0",
"private": true,
"dependencies": {
"plugincore-admin-frontend": "0.3.2"
}
}

View File

@ -1,11 +0,0 @@

`PluginCoreAdmin` is the web frontend for the `PluginCore management center (PluginCore Admin)` and should be released together.
https://github.com/yiyungent/PluginCore

View File

@ -1,54 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
---
## [unreleased]
### Documentation
- **(CHANGELOG.md)** update - ([0bbfc89](https://github.com/yiyungent/PluginCore/commit/0bbfc8955b7f6338db2125c78ec250e9eeeadcce)) - github-actions[bot]
- **(CHANGELOG.md)** update - ([4f6b47b](https://github.com/yiyungent/PluginCore/commit/4f6b47b3f86bfce4a8f660166837a7322c568d78)) - github-actions[bot]
### Miscellaneous Chores
- **(cliff.toml)** add - ([5614ef0](https://github.com/yiyungent/PluginCore/commit/5614ef024d644349095e19a0016bb23d989b0c90)) - yiyun
---
## [PluginCore.IPlugins.AspNetCore-v0.1.1](https://github.com/yiyungent/PluginCore/compare/PluginCore.IPlugins.AspNetCore-v0.1.0..PluginCore.IPlugins.AspNetCore-v0.1.1) - 2023-12-30
### Features
- **(src/**/*.cs)** // License: Apache-2.0 -> // License: GNU LGPLv3 - ([57366d3](https://github.com/yiyungent/PluginCore/commit/57366d3e2afdb8e20e94851aa8a09f1ee61b6d7e)) - yiyun
- **(src/**/*.cs)** // Project: https://moeci.com/PluginCore -> // Project: https://yiyungent.github.io/PluginCore - ([7420480](https://github.com/yiyungent/PluginCore/commit/742048065978c1b8597fab3d52f011db4247fbda)) - yiyun
### Build
- **(plugincore.iplugins.aspnetcore.csproj)** 0.1.0 -> 0.1.1 - ([338e919](https://github.com/yiyungent/PluginCore/commit/338e919c74dcff4199b82c6046a62847cc68beb3)) - yiyun
---
## [PluginCore.IPlugins.AspNetCore-v0.1.0](https://github.com/yiyungent/PluginCore/compare/PluginCore.AspNetCore-v1.0.2..PluginCore.IPlugins.AspNetCore-v0.1.0) - 2023-02-15
### Build
- **(plugincore.iplugins.aspnetcore.csproj)** `<Version>0.1.0</Version>` - ([162d304](https://github.com/yiyungent/PluginCore/commit/162d304f6b74941701b5b799228a3061ef4ea6c2)) - yiyun
---
## [PluginCore.AspNetCore-v1.0.2](https://github.com/yiyungent/PluginCore/compare/PluginCore.IPlugins.AspNetCore-v0.0.1..PluginCore.AspNetCore-v1.0.2) - 2022-04-19
### Style
- add: copyright: *.cs - ([9643dce](https://github.com/yiyungent/PluginCore/commit/9643dce112861a440d63306cb555accbed3d5111)) - yiyun
---
## [PluginCore.IPlugins.AspNetCore-v0.0.1](https://github.com/yiyungent/PluginCore/compare/PluginCore-v1.0.0..PluginCore.IPlugins.AspNetCore-v0.0.1) - 2022-04-16
### Refactoring
- 1.提取出 PluginCore.AspNetCore,PluginCore.IPlugins.AspNetCore 2.提取出更多接口,可自由替换 - ([fffd8d9](https://github.com/yiyungent/PluginCore/commit/fffd8d91c23fd6e4a4d09cbf91975beb3cf7acf0)) - yiyun
### Build
- **(plugincore.iplugins.aspnetcore.csproj)** <Version>0.0.1</Version> - ([b907263](https://github.com/yiyungent/PluginCore/commit/b9072639d894904add2faf46216e29f902ddf32b)) - yiyun
<!-- generated by git-cliff -->

View File

@ -1,28 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace PluginCore.IPlugins
{
/// <summary>
/// 目前未有效化,占坑
/// </summary>
public interface IContentFilterPlugin : IPlugin
{
Task<string> RequestBodyFilter(string urlPath, string content);
Task<string> ReponseBodyFilter(string urlPath, string content);
}
}

View File

@ -1,28 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace PluginCore.IPlugins
{
/// <summary>
/// 实验性: 不确定有效, 发现问题,请 new issue
/// </summary>
public interface IHttpFilterPlugin : IPlugin
{
Task HttpEndFilter(HttpContext httpContext);
Task HttpStartFilter(HttpContext httpContext);
}
}

View File

@ -1,39 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace PluginCore.IPlugins
{
/// <summary>
/// 实验阶段
/// <para>无法热插拔: 需要启用插件后, 再 重启站点</para>
/// </summary>
public interface IStartupPlugin : IPlugin
{
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services"></param>
void ConfigureServices(IServiceCollection services);
int ConfigureServicesOrder { get; }
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
void Configure(IApplicationBuilder app);
int ConfigureOrder { get; }
}
}

View File

@ -1,44 +0,0 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
namespace PluginCore.IPlugins
{
/// <summary>
/// 实验阶段
/// <para>热插拔: 已有效化</para>
/// </summary>
public interface IStartupXPlugin : IPlugin
{
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// <para>未有效化</para>
/// </summary>
/// <param name="services"></param>
void ConfigureServices(IServiceCollection services);
int ConfigureServicesOrder { get; }
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
void Configure(IApplicationBuilder app);
int ConfigureOrder { get; }
}
}

View File

@ -1,40 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<PackageId>PluginCore.IPlugins.AspNetCore</PackageId>
<Version>0.1.1</Version>
<Company>yiyun</Company>
<Authors>yiyun</Authors>
<Description>PluginCore.AspNetCore Plugin Sdk</Description>
<Copyright>Copyright (c) 2021-present yiyun</Copyright>
<RepositoryUrl>https://github.com/yiyungent/PluginCore</RepositoryUrl>
<PackageLicenseUrl>https://github.com/yiyungent/PluginCore/blob/main/LICENSE</PackageLicenseUrl>
<PackageTags>PluginCore PluginCore.IPlugins.AspNetCore</PackageTags>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
</PropertyGroup>
<!-- 方便开发debug,与发布到nuget -->
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="PluginCore.IPlugins" Version="0.9.1" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\PluginCore.IPlugins\PluginCore.IPlugins.csproj" />
</ItemGroup>
<!-- 生成注释xml -->
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
<DocumentationFile>bin\Release\netstandard2.0\PluginCore.IPlugins.AspNetCore.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Folder Include="Infrastructure\" />
<Folder Include="Interfaces\" />
<Folder Include="Models\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
</ItemGroup>
</Project>

View File

@ -1,82 +0,0 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
# template for the changelog header
header = """
# Changelog\n
All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.\n
"""
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
---
{% if version %}\
{% if previous.version %}\
## [{{ version | trim_start_matches(pat="v") }}]($REPO/compare/{{ previous.version }}..{{ version }}) - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% endif %}\
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits
| filter(attribute="scope")
| sort(attribute="scope") %}
- **({{commit.scope}})**{% if commit.breaking %} [**breaking**]{% endif %} \
{{ commit.message }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {{ commit.author.name }}
{%- endfor -%}
{% raw %}\n{% endraw %}\
{%- for commit in commits %}
{%- if commit.scope -%}
{% else -%}
- {% if commit.breaking %} [**breaking**]{% endif %}\
{{ commit.message }} - ([{{ commit.id | truncate(length=7, end="") }}]($REPO/commit/{{ commit.id }})) - {{ commit.author.name }}
{% endif -%}
{% endfor -%}
{% endfor %}\n
"""
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# remove the leading and trailing whitespace from the templates
trim = true
# postprocessors
postprocessors = [
{ pattern = '\$REPO', replace = "https://github.com/yiyungent/PluginCore" }, # replace repository URL
]
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/orhun/git-cliff/issues/${2}))"}, # replace issue numbers
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features" },
{ message = "^fix", group = "Bug Fixes" },
{ message = "^doc", group = "Documentation" },
{ message = "^perf", group = "Performance" },
{ message = "^refactor", group = "Refactoring" },
{ message = "^style", group = "Style" },
{ message = "^revert", group = "Revert" },
{ message = "^test", group = "Tests" },
{ message = "^chore\\(version\\):", skip = true },
{ message = "^chore", group = "Miscellaneous Chores" },
{ body = ".*security", group = "Security" },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"

View File

@ -1 +0,0 @@
dotnet pack -c Release

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