内置特性
# 后端插件开发
# 日志管理
Web API以及网关服务调试日志输出统一通过/IoTCenterWeb/publish下logsetting.json文件配置管理,平台使用Serilog管理日志文件。
# 平台日志输出
若需调试Web API正常启动前后相关服务以及插件详细日志输出,修改日志配置文件最小级别为Debug。
# 插件日志输出
我们暴露了位于IoTCenter.Utilities命名空间里的ILoggingService日志接口,以便在实际业务中进行日志调试、业务日志和平台关键操作记录存储。
# 插件独享日志
重载函数中包含上下文即(context)的标识参数可作为插件独享日志文件输出,我们约定规范context为插件程序集名称。
提示
context参数建议固化为插件程序集名称,勿给定类似随机值,底层会针对此参数不同而每次实例化日志配置且缓存。
# 关键操作日志
当对平台重要业务进行操作时,我们需要详细记录操作以便审计可追溯,例如远程访问的IP和端口,是什么账号,操作什么资源,做了什么事件,操作后的结果。我们通过日志接口中提供的审计方法(Audit)来进行详细记录。
# 常用帮助扩展
# 字符串扩展
我们提供位于IoTCenter.Utilities.Extensions命名空间里对于字符串的扩展方法。例如,空字符串判断(IsEmpty)、分隔字符串(SplitString)、分隔字符串并去重(SplitStringWithDistinct)、分隔字符串并转int数组(SplitStringToIntArray)、字符串转int(ToInt),字符串转decimal(ToDecimal)、字符串转DateTime(ToDateTime)、字符串转double(ToDouble)等。
# 日期扩展
我们提供位于IoTCenter.Utilities.Extensions命名空间里对于日期的扩展方法。例如将日期转换为时间戳(ToUnixTimestamp),反之时间戳转换为日期(FromUnixTimeStampMillisecond)、日期转换为秒(GetUnixTimeStampSeconds)、日期转换为毫秒(GetUnixTimeStampMilliseconds)等
# 序列化扩展
我们提供位于IoTCenter.Utilities命名空间里的序列化扩展方法。例如对象转JSON字符串(ToJson),反之JSON字符串反序列化为对象(FromJson)等。
# 枚举扩展
我们提供位于IoTCenter.Utilities命名空间里对于枚举的扩展方法。例如获取枚举的描述信息(GetDescription)、获取枚举的描述信息(GetDisplayName)等。
# 键值对对象
我们提供位于IoTCenter.Utilities命名空间里对于前后端交互或存储等时常涉及到的键值对属性数据结构。例如需要使用Id和Name属性的键值对(IdNameObject)、 Key和Value属性的键值对(KeyValueObject)、Name和Value属性的键值对(NameValueObject)。
# 会话信息
我们提供位于System命名空间里的Session类且通过构造函数注入来获取每一请求上下文中用户相关信息,例如获取当前操作账号、角色名,是否为管理员,访问IP和端口信息。
注意
比如在Job等中获取会话信息必然抛空引用异常,这是极其错误的做法,因为会话基于请求上下文,勿随意滥用。
# 读取配置
必须定义顶级根节点,建议以插件全名称或后缀名称作为顶级根节点,否则当加载多个插件,读取同名节点时将会被覆盖。比如读取如下根节点下Url值。
底层暴露IIotConfiguration接口,将其通过构造函数注入,使用方式与IConfiguration接口等同。
# 事件总线
引入事件总线设施从而降低代码耦合或削峰等,当前版本支持本地事件总线和Kafka分布式事件总线。所有事件总线定义以及订阅处理规范约定放于IntegrationEvents目录,Events目录为事件定义,EventHandlers目录为事件处理程序即订阅事件,同时我们约定规范事件名称以点号隔开。
注意
事件总线订阅处理程序做好幂等性设计,所谓幂等性即执行一次或多次,其影响是相同的,换言之,重复执行也不影响实际业务。
# 本地事件总线
模拟定义添加用户后给用户发送邮件事件
实现ILocalEventHandler本地事件处理接口,其泛型类为对应要接收的事件。
最后在Configure方法中通过底层暴露的本地事件总线接口,订阅当添加用户后,给用户发送邮件事件处理。
注意
非跨库订阅即发布和订阅事件处理程序在同一插件内,事件类可无需显式声明EventName事件名称特性,但前置条件需Web Api发布目录下IoTCenterCore.EventBus>=6.1.5
# 全局订阅
首次加载插件时,立即全局订阅事件处理。
# 局部订阅
也可以在具体接口实现中,按需局部订阅事件,比如当添加用户成功后才去订阅事件处理。
当添加用户成功后,发布给用户发送邮件事件。
# 跨库订阅
一般来讲,事件发布和订阅所属事件类型应为相同类型,但在插件化中存在事件发布和订阅处于不同库场景,此时发布事件和订阅事件类型不同,但属性一致,为避免代码耦合即解耦,底层设施额外扩展此场景。
场景假设:在插件库Ganweisoft.IoTCenter.Module.Test1发布触发发送用户邮件事件,而在Ganweisoft.IoTCenter.Module.Test2订阅用户发送邮件事件处理。
我们在Ganweisoft.IoTCenter.Module.Test1插件中发布用户邮件事件。
定义发布事件类,基础设施暴露EventName特性,通过EventName特性指定发布事件名称。
在Ganweisoft.IoTCenter.Module.Test2插件中订阅以及进行订阅用户邮件事件处理。
此插件中同样使用EventName特性指定相同订阅事件名称。
说明
上述发布和订阅示例皆为一对一关系,对于一对多关系即一处发布多处订阅同样支持。
# 模糊订阅
订阅指定具体事件名称仍无法充分满足实际业务需求,我们针对本地事件总线提供三种模糊订阅策略增强式支持,前缀式订阅【显式指定(.*)】,后缀式订阅【显式指定(*.)】,全量式订阅【显式指定(*)】。
例如,将Ganweisoft.IoTCenter.Module.Test2插件中订阅事件名称显式指定为*,即可全量式订阅所有发布事件。
注意
前缀式和后缀式订阅同理,前缀式订阅具体指,比如订阅敢为云发布的邮件和短信服务通知,敢为云发布邮件和短信事件名称定义为ganweicloud.send.mail和ganweicloud.send.sms,可使用前缀式模糊订阅这2个事件,例如:ganweicloud.send.*。
# Kafka分布式事件总线
# 配置说明
应用商店下载Kafka应用插件,常见使用场景:网关发布,Web API插件订阅;Web API插件发布和订阅;其他应用发布,Web API插件订阅等;
字段 | 说明 |
---|---|
BootstrapServers | Kafka地址 |
GroupId | Kafka消费组Id |
示例1:使用默认配置连接到本地 kafka 服务器的最小化配置。
示例2:Default节点配置对应Kafka中的ClientConfig,比如指定套接字超时。
示例3:如果需要连接到本地主机以外的其他服务器,则需要配置连接属性,指定主机名(作为IP地址)。
# 使用说明
发布和订阅处理程序与本地事件总线使用方式类似,只需将本地事件总线接口ILocalEventBus替换为分布式事件总线接口IDistributedEventBus,本地事件处理程序接口ILocalEventHandler替换为分布式事件总线处理程序接口IDistributedEventHandler。
我们发布如下分布式事件示例,在执行该示例接口前请确保Kafka服务处于正常运行状态,同时已安装Kafka订阅插件以及相关配置正确。
注意
分布式事件总线订阅不支持模糊订阅策略,仅支持一对一和一对多关系的显式指定具体事件名称订阅。
# 定时作业
利用https://github.com/atifaziz/NCrontab库实现计划作业表达式,包含秒级定义即支持6部分表达式定义(底层作业滚动周期为10s,所以自定义实现定时作业应>=10s)。
# 使用示例
底层暴露IBackgroundTask接口,通过BackgroundTask特性配置Corn表达式。
定时作业以单例形式注入
# 使用注意
在实际使用过程中,可能会出现定时任务仅运行一次或不运行情况,基于底层定时任务实现逻辑。
列举如下三点使用注意事项:
- 插件定时任务以【插件程序集名称+定时任务类名】作为计划名称,所以各插件中计划名称不可重复。
- DoWorkAsync方法调用由底层根据滚动周期自动调用,切不可在其方法中再次使用While,否则会完全阻止其他任务调用。
- DoWorkAsync方法建议尽可能使用异步方法,否则若业务逻辑耗时太长,会阻塞其他任务调用。
提示
若上述底层提供的作业无法满足实际业务要求,可使用Quartz代替。
# 数据转换对象(DTO)
对数据进行DTO,存在各式各样包,基于此,底层统一规范使用AutoMapper来进行映射,关于AutoMapper这里不再做详细介绍,若不熟悉,请自行查阅相关资料并了解,为考虑到不同场景或简化等原因,底层提供多种映射使用方式
我们使用如下源目标和和映射目标作为示例演示
# 映射配置
底层暴露IAutoMapperConfig接口,创建映射配置类实现该接口,在此类中全局配置插件库中所有映射,配置类名称推荐以【插件名 + AutoMapperConfig】命名。
AutoMapper对于属性忽略映射,使用如下: .ForMember(d => d.Id, o => o.Ignore()) 为进一步简化书写,底层额外扩展封装Ignore方法,使用如下: .Ignore(d => d.Id)
例如,如下所示,我们仅配置映射属性Name,但忽略映射Id。
# 映射方式(一)
底层通过对外暴露IObjectMapping接口来进行对象映射,如下以构造函数注入使用。
调用接口中的Map方法完成从源目标到映射目标的数据映射,此方式乃AutoMapper内置映射使用方式。
同样,通过Postman来模拟验证测试。
# 映射方式(二)
上述为AutoMapper框架默认映射方式,但底层额外基于object提供了扩展方法,更加方便简洁化,所以我们不再通过IObjectMapping接口进行映射。
比如上述通过源对象CreateUserDTO映射到目标对象UserTest,导入命名空间【IoTCenterCore.AutoMapper】,然后调用MapTo扩展方法,我们可修改为如下:
调用MapToList方法将映射源集合映射到目标集合。
# 国际化语言
# 翻译文件说明
前端页面内容和相关操作等提示都由后台统一返回,我们将翻译分为公共翻译和插件翻译两部分。Ganweisoft.IoTCenter.Module.Localization插件作为国际化支持入口,其Localization目录作为全局国际化公共翻译。
zh-CN文件:公共中文翻译 en-US文件:公共英文翻译
指定插件国际化翻译目录如下:
# zh-CN目录
- Back:后台插件中文翻译
- Front:前端UI中文翻译
# en-US目录
- Back:后台插件英文翻译
- Front:前端UI英文翻译
注意
- JSON翻译文件命名约定:英文且小写。
- 前端文件命名约定【前端插件名.子菜单名.json】
- 后台插件命名约定【插件名.json】
# 使用示例
以设备列表英文翻译为例,作进一步详细说明
- 前端UI
- 后端响应
翻译模板json数组内容:
msgid "填写翻译键" msgstr "填写翻译值" 使用.NET Core框架所提供【IStringLocalizer<T>】构造函数注入,进行翻译。
private readonly IStringLocalizer<T> _stringLocalizer;
# 公共翻译内容
建议翻译键以【common_】作为前缀,其他单词通过下划线隔开。
若需响应参数不正确或参数为空或不能为空等,对应公共翻译键如_stringLocalizer["common_parameter_is_incorrect"]。
# 插件翻译内容
建议翻译键以【插件名简称_】作为前缀,其他单词通过下划线隔开。
比如翻译设备不存在,_stringLocalizer["equip_config_device_not_exist"]。
# 插件翻译配置
旧版本后端JSON翻译文件格式(兼容但废弃,新开发插件请勿继续使用):msgid为键,msgtr为值
但这样一来我们需要显式增加zh-CN(中文)翻译配置文件,同时对开发人员看代码时若翻译键模糊不清导致不明白具体翻译内容,面向研发体验不友好。 所以基于以上2点,我们针对翻译配置做出进一步优化:
- 数据结构:JSON数组调整为对象
- 翻译格式:键值对格式调整为对象格式,以中文为翻译键,值为翻译值
- 默认翻译:当未找到中文翻译键对应的值时,默认以中文翻译键作为值返回
新版本后端JSON翻译文件格式优化如下
# 文件统一管理
为便于统一管理文件以及屏蔽重复操作文件逻辑,平台开放式提供统一文件上传存储管理等功能
# 物理存储
物理存储目录可在./IoTCenter/data/AlarmCenter 目录中的AlarmCenterProperties.xml文件进行灵活配置
<StorageFile value="D:\ganwei\IoTCenter\StorageFile" />
# 数据库查询
我们可直接操作数据库表gwfilestorage查看上传的相关文件信息,表列详细说明如下
列名 | 列类型 | 备注 |
---|---|---|
id | string | 主键,32位guid |
full_path | string | 磁盘实际物理路径 |
upload_time | datetime | 上传时间 |
file_name | string | 文件名称 |
extension_name | string | 扩展名/文件类型 |
subject | string | 主题 |
access_path | string | 虚拟目录访问路径 |
注意
- 登录插件6.1.1.75版本支持文件上传限制参数。
- 在/IoTCenterWeb/publish文件夹中的appsettings.json可配置允许上传的类型。 ✳为不限制文件上传类型,配置为✳时请勿另外配置类型。
"FileStorageOptions": {
"FileTypeControl": [
"*"
], // 允许上传的文件类型,包含*时表示不限
"FileCountLimits": 10, //批量上传文件最大限制
"FileSizeLimits": 10240 //文件大小限制,单位kb
}
或
"FileStorageOptions": {
"FileTypeControl": [
"png",
"jpg",
"jpeg",
"zip",
"txt"
], // 允许上传的文件类型,包含*时表示不限
"FileCountLimits": 10, //批量上传文件最大限制
"FileSizeLimits": 10240 //文件大小限制,单位kb
}
# 文件上传
# 接口名称
/api/fileStorage/upload
# 请求方式
POST
# 请求参数说明
content-Type:multipart/form-data
表单提交
属性名 | 属性类型 | 说明 |
---|---|---|
formFile | file | 文件,可批量上传 |
subject | string | 主题 |
响应参数说明 content-Type:application/json
属性名 | 属性类型 | 说明 |
---|---|---|
code | int | 200成功,其他失败 |
succeeded | bool | 是否成功 |
message | string | 说明 |
data | List< objec> | 消息体对象集合 |
data=>id | string | 图片id |
extensionName | string | 文件扩展名/类型 |
subject | string | 主题 |
fileName | string | 文件名 |
accessPath | string | 回显地址 |
data < object > 消息体对象示例
# 文件下载
# 接口名称
/api/fileStorage/download/{id}
# 请求方式
GET
# 请求参数说明
请求参数位置为 route参数
属性名 | 属性类型 | 说明 |
---|---|---|
id | string | 图片id |
响应参数说明 content-Type:application/octet-stream 二进制数据,无数据时,响应HTTP状态码为204
# 获取缩略图
# 接口名称
/api/fileStorage/getThumbnail/{id}
# 请求方式
GET
# 请求参数说明
route参数
属性名 | 属性类型 | 说明 |
---|---|---|
id | string | 图片id |
query参数
属性名 | 属性类型 | 说明 |
---|---|---|
width | int | 宽像素 |
height | int | 高像素 |
响应参数说明
content-Type:image/图片文件扩展名 无数据时 HTTP状态码为204,尝试获取非图片格式文件时返回400 HTTP状态码
# 获取图片预览
# 接口名称
/api/fileStorage/preview/{id} 支持以下图片格式 { ".png", ".jpg", ".jpeg" }
# 请求方式
GET
# 请求参数说明
route参数
属性名 | 属性类型 | 说明 |
---|---|---|
id | string | 图片id |
响应参数说明 content-Type:image/图片文件扩展名 无数据时,HTTP状态码为204,尝试获取非图片格式文件时返回400 HTTP状态码
# 其他特性
# 数据库表结构自动迁移
通过NuGet下载IoTCenterCore.EntityFrameworkCore.AutoMigrator包最新版本。
在Startup类中构建管道方法(Configure)中配置如下一行
builder.UseAutoMigrator<TestDbContext>();
注意
- 仅支持自动创建表或自动创建列。
- 字段类型调整以及列的重命名、删除列等都涉及到对表进行破坏性变更,实际生产环境存在重大风险,所以忽略处理。
# 匿名安全扫描
为加强平台匿名接口安全性,现增加自动化扫描所有插件匿名接口功能,启用该功能需具备如下两个条件:
Web Api运行目录下IoTCenterCore.dll文件版本:6.1.4+(含6.1.4版本)
Web Api运行目录下appsettings.json配置文件,将ScanAnonymousApi节点值修改为true
启动平台访问Web时将会在Web Api运行目录下生成名为anonymous_api_description.json文件,数据结构示例如下