后端开发
1. 插件二开规范
- 统一术语约定:
框架级别标识:
-- Pedestal : 基座标识
-- StandardPlugin :标准化插件标识
-- CustomMadePlugin :定制化插件标识
-- ExternalPlugin :外部二开插件标识
框架级别标识用途说明:通过框架级别标识 对CMS2.0软件系统的数据、项目信息进行标识,完成数据的隔离,保证软件的数据及信息安全;
- CMS2.0软件系统需要遵循的开发规范
【强制】缓存的键命名需要遵循以下格式:
框架级别标识:插件标识:业务标识:项目标识【选填】:数据标识【选填】
【强制】插件打包必须要遵循以下的规范, 打包配置文件中的HotPlugId属性信息 需要遵循以下格式:
框架级别标识_项目或客户标识【选填】_插件标识
【强制】如果插件与插件、插件与基座 的业务流程需要数据保证强一致性,需要使用基座提供分布式事务总线,确保跨模块/插件 间的信息可追溯,防止在出现最坏情况时可以人工介入恢复数据;
**备注:**由于基座尚未支持分布式事务总线,所以该规范暂时不用支持。
【重要不强制】如果插件需要对数据库进行初始化或者迁移的操作,需要继承基座提供的IProjectRuntimeMigrator(运行时迁移器)这个抽象类,并在子类实现中完成插件数据库迁移的操作;
【重要不强制】插件的业务数据库命名应遵循以下格式
项目标识_后缀标识
-- 项目标识:项目id
-- 后缀标识:推荐使用project
【重要不强制】插件的业务数据库表命名应遵循以下格式
插件前缀_业务表名
【重要不强制】如果使用ef core ORM框架的code-first方式,插件对应的数据迁移表应该使用__efmigrationshistory_equipmentmaintenance格式来命名迁移表,equipmentmaintenance为插件标识。这样命名可以防止当存在多个插件时出现迁移数据表数据错乱。
2. 插件开发环境
- 技术栈: 微软.net技术栈;
- 目标框架: .net6、asp.net core webapi
- 开发IDE: Visual Studio 2022
- 开发语言:C#
- 安装CMS2.0基座
- 数据库支持:mysql 8.0+; sql server 2016+
- 插件项目模板安装
打开powershell终端,执行以下命令,可以完成模板安装
dotnet new install CMS.Plugin.Template.Simple --nuget-source https://nexus.sycdev.com/repository/nuget-group/index.json
模版卸载命令
dotnet new uninstall CMS.Plugin.Template.Simple
3. 快速入门
3.1 创建项目
3.1.1 根据模版创建插件项目(最佳实践)
模板模板示例说明:
1.实现了基座的插件接口, 完成与基座集成;
2.提供了一个完整的增删改查功能,给二次开发提供指引;
3.提供简单、便捷、易于扩展的项目分层架构;
- 打开PowerShell命令行终端,创建项目
执行以下命令
dotnet new cms-plugin-sim -n Demo -p Sample
创建出一个解决方案标识为Demo, 插件项目标识为Sample 的工程
参数选项说明:
-n : 解决方案标识
-p : 插件项目名称标识
-
解决方案结构说明
**框架依赖包:**最少需要引用CMS2.0框架的以下依赖包:CMS.Framework.AspNetCore 、CMS.Extensions.Variable、CMS.Extensions.Data、SYC.Plugin、CMS.Unit.Authority、CMS.Data.Stressing
**接口控制器层:**存放插件开发的接口控制器;
**业务领域层:**存放插件业务核心设计模型及代码实现,按业务模型及功能以文件夹进行分类,比如模型对象、数据库访问器、管理器、业务服务、适配器等等;
**多语言翻译文件层:**存放多语言文件;
**数据库迁移文件:**存放ef core code-first方式生成的数据库迁移文件;
**辅助设施层:**存放帮助类、工具类、公共类等;
**框架接口实现层:**CMSPluginEntry、CMSPluginProjectService、PluginDbContextMigrator 实现了基座提供的核心接口,实现插件和基座集成以及通信;
PluginEntry:插件入口抽象类,插件必须实现了该抽象类才完成与基座集成;
BaseProjectService:插件服务抽象类,插件实现了该抽象类,则该插件可以具备基座提供的协议基础能力,比如插件授权、可以在前端 UI 上控制服务启停;
IProjectRuntimeMigrator:项目运行时迁移器接口,插件如果需要进行数据迁移则需要实现该接口,并在实现的接口方法中完成插件数据迁移操作;
- 调试配置
(1)对插件项目工程进行配置,将插件编译输出目录输出到基座所在的安装目录中,如下图:

(2)启动基座,配置服务端口及数据库

(3)编译插件项目,附加到基座启动的服务进程进行插件开发调试,vs选择 调试—>附加到进程 选择目标进程后可以对插件进行断点调试

(4)以swagger作为接口文档,运行基座后通过http://127.0.0.1:18000/swagger/index.html 地址访问swagger文档
(4.1)可以通过swagger对外提供接口文档;
(4.2)可以通过swagger发起接口调用,测试调试接口;进行接口调用时需要对接口进行一次授权,如下图:

配置完成后,可以通过swagger进行接口调用,可以对插件程序进行断点调试,如图:


- 插件服务发布
通过插件模版创建的项目在根目录中,提供了发布脚本publish.ps1,鼠标选择publish.ps1 右键—>使用PowerShell运行,会对插件服务进行打包输出到插件根目录output\publish文件夹下,示例如图:

4. 重点的基础设施
-
当前项目工程
-
数据库连接获取
-
当前用户
-
插件数据库迁移或种子数据初始化
-
插件配置
-
业务/分布式事务
-
文件系统
下文出现的 serviceProvider 是指.net6框架 IServiceProvider的类型对象,用于依赖注入需要的对象类型;
4.1 当前项目工程
CMS2.0在同一时刻只支持打开一 个项目工程,当前运行的项目工程信息在系统中非常重要,与许多业务实现密切相关。CMS2.0系统会将当前项目运行信息抽象封装为IProjectRunner(项目运行者)模型,开发者可以通过IProjectRunner模型对象拿到关注的相关信息;
示例伪代码:获取当前项目工程id
//1.通过依赖注入方式注入 IProjectRunner
var projectRunner = serviceProvider.GetRequiredService<IProjectRunner>();
//2.获取当前项目工程的id
var currentProject = projectRunner.Current.Info.Id;
//3.相关业务处理
相关的信息可以通过转到定义根据代码实现进行查看获取
4.2 数据库连接获取
插件和基座需要使用相同的数据库服务,同时CMS2.0的数据需要以项目进行隔离。基座提供了根据数据库名称获取数据库连接字符串的封装。
示例伪代码:根据插件的数据库标识获取插件数据库的连接字符串
//1.从依赖注入容器中注入IProjectConnectionStringProvider
var projectConnectionStringProvider = serviceProvider.GetRequiredService<IProjectConnectionStringProvider>();
//2.根据数据库标识获取与项目关联的数据库的连接
var connectString = projectConnectionStringProvider.CreateConnectionString("formula");
//3.TODO:自己的业务处理
4.3 当前用户
在前后端分离架构的应用程序中检索有关已登录用户的信息是很常见的. 当前用户是与应用程序中的当前请求相关的活动用户。
示例代码:获取当前请求用户信息
//1.以构造方法方式注入ICurrentUser;需要引用CMS.Framework.AspNetCore NuGet包
ICurrentUser currentUser = ......
//2.获取用户身份信息,目前支持获取用户id、用户账号
var userId = currentUser.Id;
var userAccount = currentUser.UserAccount;
//3.TODO:自己的业务处理
4.4 数据库备份
基座提供了数据库备份的能力,如果插件数据库需要备份,需要遵循基座的数据库备份约定。
示例伪代码:插件实现数据库备份的核心要点
1.需要在插件注册服务的方法中 注册IDataPackageFactory
context.Services.AddScoped<IDataPackageFactory>(p =>
new DatabasePackageFactory(PluginConstants.PluginIdentity, PluginConstants.PluginName, DataTypes.Plugin, "_project", p));
new DatabasePackageFactory参数说明:
-- PluginConstants.PluginIdentity:插件的标识
-- PluginConstants.PluginName:插件名称
-- DataTypes.Plugin : 类别
-- "_project" : 插件数据库后缀
2.需要在插件加载启用时上报插件的信息
context.GetRequiredService<DataStoreManager>()
.Add(new DataStoreGroup(PluginConstants.PluginIdentity, PluginConstants.PluginName, DataTypes.Plugin));
new DataStoreGroup参数说明:
-- PluginConstants.PluginIdentity:插件的标识
-- PluginConstants.PluginName:插件名称
-- DataTypes.Plugin : 类别
3. 1和2的参数PluginConstants.PluginIdentity、PluginConstants.PluginName需要一致
示例代码
using CMS.Data.Stressing;
using CMS.Plugin.sample.Domains.Models.Consts;
using Microsoft.Extensions.DependencyInjection;
using Structing.Core;
using Structing.Web;
using SYC.Plugin;
namespace CMS.Plugin.sample
{
/// <summary>
/// 示例插件,使用<see cref="EnableApplicationPartAttribute"/>将导入Controller
/// </summary>
[EnableApplicationPart]
public class CMSPluginEntry : PluginEntry
{
/// <summary>
/// 注册服务和配置
/// </summary>
/// <param name="context"></param>
public override void Register(IRegisteContext context)
{
//注册插件的备份功能,通过基座备份的能力完成插件的数据备份
context.Services.AddScoped<IDataPackageFactory>(p =>
new DatabasePackageFactory(PluginConstants.PluginIdentity, PluginConstants.PluginName, DataTypes.Plugin, "_project", p));
base.Register(context);
}
/// <summary>
/// 配置管道和服务
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override Task ReadyAsync(IReadyContext context)
{
//向基座提供插件备份的相关信息,用于插件数据备份
context.GetRequiredService<DataStoreManager>()
.Add(new DataStoreGroup(PluginConstants.PluginIdentity, PluginConstants.PluginName, DataTypes.Plugin));
return base.ReadyAsync(context);
}
}
}
5.5 插件数据迁移
数据库的结构及种子数据迁移是软件系统的常规操作。基于CMS2.0软件系统的架构设计,插件数据迁移需要遵循基座的数据迁移约定。需要继承基座提供的IProjectRuntimeMigrator接口并在约定方法中完成数据库的架构及种子数据迁移。
示例伪代码:插件数据执行数据迁移的核心要点
1.插件继承基座的IProjectRuntimeMigrator接口
2.在实现的public async Task UpgradeAsync(Project.Project project, IServiceProvider serviceProvider)方法中执行插件的数据迁移操作
示例代码:
using CMS.Plugin.sample.Domains.DataAccesses.DataSeeds;
using CMS.Project.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace CMS.Plugin.Sample.Data
{
/// <summary>
/// 工程数据库迁移器,当<see cref="IProjectRunner"/>加载工程时此对象会被创建调用
/// </summary>
public class PluginDbContextMigrator : IProjectRuntimeMigrator
{
private readonly ILogger<PluginDbContextMigrator> _logger;
public PluginDbContextMigrator(ILogger<PluginDbContextMigrator> logger)
{
_logger = logger;
}
/// <summary>
/// 执行迁移
/// </summary>
/// <param name="project">当前要运行的工程</param>
/// <param name="serviceProvider">存 在运行工程上下文的serviceProvider</param>
/// <returns></returns>
public async Task UpgradeAsync(Project.Project project, IServiceProvider serviceProvider)
{
var cmsPluginDataSeed = serviceProvider.GetRequiredService<ICmsPluginDataSeedService>();
await cmsPluginDataSeed.SeedAsync();
}
}
}