Compare commits

...

10 Commits

Author SHA1 Message Date
hello
bfc15537da 修改笔记内容 2025-04-03 06:13:07 +08:00
hello
b354b70f7e
Delete .vscode directory 2025-03-28 10:10:29 +08:00
hello
16a14d3b21
Delete blogs/abc.md 2025-03-28 10:10:03 +08:00
hello
08e5adaac5
Update rabbitmq-management-mqtt.md 2025-03-28 10:09:36 +08:00
hello
9925eaf091
Delete notes/shuiyifang directory 2025-03-28 10:08:50 +08:00
hello
4443d55057 修改数据库迁移脚本 2025-03-25 15:24:22 +08:00
hello
0b1c871c7e 数据库迁移和历史数据生成 2025-03-25 15:23:22 +08:00
hello
bfd1ee0547 引入混合缓存 2025-03-25 14:43:13 +08:00
hello
28523c58b2 关于 Aspire 应用宿主的编排 2025-03-18 22:45:44 +08:00
hello
44e0ba65f0 时间提供者设计 2025-03-18 18:35:35 +08:00
9 changed files with 251 additions and 114 deletions

View File

@ -1,3 +0,0 @@
{
"livePreview.defaultPreviewPath": "/notes/helloshop/index.html"
}

View File

@ -1,47 +0,0 @@
# 零度框架升级 8.0 稳定版
随着微软 Aspire 正式版的发布,零度框架 HelloShop 8.0 版本也正式发布了,本次改进高达 180 多项,代码已推送到 Github主要包括以下几个方面。
## 升级到最新稳定版
之前,零度框架使用的是 Aspire 的多个预览版,导致了一些不稳定的问题,现在,零度框架和所有依赖包都升级到了最新稳定版,不再引用任何预览包,卸载掉 Visual Studio 2022 预览版,并升级到 17.10.0 稳定版,卸载掉所有 .NET 9.0 预览版,安装 .NET 8.0 SDK 稳定版,卸载掉所有 Aspire 预览版,将 Aspire 工作负载升级到 8.0.0 稳定版。项目所依赖的所有 NuGet 包都升级到了最新稳定版。
现在,零度框架和所有依赖包都升级到了最新稳定版,不再引用任何预览包。
## 远程权限检查改进
`PermissionsController``RemotePermissionChecker` 引入 RoleId 角色权限检查,
## 创建虚拟授权类用于测试
使用 `FakePermissionChecker` 类来模拟权限检查,方便后续单元和集成测试。
## 创建 Aspire 集成测试项目
创建 `HelloShop.FunctionalTests` 项目,该项目使用 Aspire 模板创建,用于集成测试,包括了一个 `FirstWebApiIntegrationTest` 测试类,用于测试所有微服务 Web API 的集成。
## 创建产品单元测试项目
创建 `HelloShop.ProductService.UnitTests` 项目,该项目使用 xUnit 模板创建,用于单元测试产品微服务中的代码,包括了一个 `ProductsControllerTest` 测试类,用于演示使用内存数据库模拟数据进行单元测试。包括一个 `MockProductsControllerTest` 测试类,用于演示使用 Moq 框架模拟数据进行单元测试。
## 创建产品集成测试项目
创建 `HelloShop.ProductService.FunctionalTests` 项目,该项目使用 xUnit 模板创建,用于集成测试产品微服务中的代码,包括了一个 `BrandApiIntegrationTest` 测试类,用于演示产品品牌 API 接口的集成测试,为了提高集成测试效率,使用 SQLite 内存数据库模拟数据,同时使用 `WebApplicationFactory` 类来模拟 Web 主机,以便在集成测试中使用真实的 HTTP 请求。通过使用 `WebApplicationFactory` 类,实现自动登录和令牌传递,以便在集成测试中使用真实的用户身份。
## 项目启动端口配置
为了项目更好的同时兼容 HTTP 和 HTTPS每个微服务 `launchSettings` 文件中添加了 HTTP 和 HTTPS 端口配置,并约定 80xx 为 HTTP 端口81xx 为 HTTPS 端口, 任何微服务都可通过两种协议访问以便于测试。
## 修复由于包升级导致的编译错误
这些升级包括 OpenApi 界面、删除过期的代码,修复由于包升级导致的编译错误。
## MAUI 项目升级
将 MAUI 项目升级到最新稳定版,修复由于包升级导致的编译错误。
## 后期版本计划
后期内容包括Blazor 中控界面搭建、前端认证授权, MAUI 跨平台开发、安卓、IOS、MacOS、Windows 跨平台界面制作后台自动任务管理、gRPC 微服务最佳实践、基于 Dapr 的事件服务总线和密钥管理、基于 Orleans 的分布式计算、基于 Aspire 项目的单机部署, K8S 集群容器化部署和 CI/CD 最佳实践,使用 OpenTelemetry 实现监控和追踪,使用 Prometheus 和 Grafana 实现监控和报警,分布式日志链路跟踪,性能追踪。

View File

@ -82,7 +82,7 @@ var connection = builder.AddConnectionString("myconnection");
builder.AddProject<Projects.ApiService>("api").WithReference(connection);
```
## 连接字符串和终结点引用
## 终结点引用
```csharp
var builder = DistributedApplication.CreateBuilder(args);
@ -98,7 +98,7 @@ var apiservice = builder.AddProject<Projects.AspireApp_ApiService>("apiservice")
## 在 Redis 组件上启用管理扩展
```csharp
var redis = builder.AddRedis("redis").WithManagement();
var redis = builder.AddRedis("redis").WithRedisInsight();
var redis = builder.AddRedis("redis").WithRedisCommander();
```

View File

@ -0,0 +1,25 @@
<mxfile host="65bd71144e">
<diagram id="IbUPyTJ7BBrHTTByFtqF" name="Page-1">
<mxGraphModel dx="1261" dy="1139" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="8" style="edgeStyle=none;html=1;" edge="1" parent="1" source="4" target="7">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="4" value="Service 1" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="40" y="40" width="270" height="190" as="geometry"/>
</mxCell>
<mxCell id="9" style="edgeStyle=none;html=1;" edge="1" parent="1" source="5" target="7">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="5" value="Service 2" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="40" y="290" width="270" height="190" as="geometry"/>
</mxCell>
<mxCell id="7" value="Redis" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="450" y="180" width="250" height="170" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@ -0,0 +1,84 @@
# 使用 HybridCache 混合缓存库
混合缓存可以同时使用内存缓存和分布式缓存,它可以提高缓存的命中率,减少对分布式缓存的访问,从而提高性能。
- 当从缓存中获取数据时,它首先会从内存缓存中获取数据,如果内存缓存中没有数据,它会从分布式缓存中获取数据。
- 当向缓存中写入数据时,它会同时向内存缓存和分布式缓存中写入数据。
```text
Service 1 ----> HybridCache ----> MemoryCache ----> DistributedCache
```
https://learn.microsoft.com/zh-cn/azure/architecture/best-practices/caching
## 安装程序包
```shell
dotnet add package Microsoft.Extensions.Caching.Hybrid
```
## 配置服务
```csharp
builder.Services.AddHybridCache();
```
## 使用服务
```csharp
public class SomeService(HybridCache cache)
{
private HybridCache _cache = cache;
public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
{
return await _cache.GetOrCreateAsync( $"{name}-{id}",async cancel => await GetDataFromTheSourceAsync(name, id, cancel),cancellationToken: token );
}
public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
{
string someInfo = $"someinfo-{name}-{id}";
return someInfo;
}
}
```
## 按标记移除缓存条目
```csharp
public class SomeService(HybridCache cache)
{
private HybridCache _cache = cache;
public async Task<string> GetSomeInfoAsync(string name, int id, CancellationToken token = default)
{
var tags = new List<string> { "tag1", "tag2", "tag3" };
var entryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(1),
LocalCacheExpiration = TimeSpan.FromMinutes(1)
};
return await _cache.GetOrCreateAsync(
$"{name}-{id}", // Unique key to the cache entry
async cancel => await GetDataFromTheSourceAsync(name, id, cancel),
entryOptions,
tags,
cancellationToken: token
);
}
public async Task<string> GetDataFromTheSourceAsync(string name, int id, CancellationToken token)
{
string someInfo = $"someinfo-{name}-{id}";
return someInfo;
}
public async Task RemoveByTagAsync(string tag)
{
await _cache.RemoveByTagAsync(tag);
}
}
```

View File

@ -0,0 +1,137 @@
# 自动迁移和数据库初始化
## 生成迁移脚本
```shell
dotnet ef migrations add InitialCreate --output-dir Infrastructure/Migrations
```
## 手动将最新迁移脚本应用到数据库
```shell
dotnet ef database update
```
## 使用代码创建数据库
```csharp
await dbContext.Database.EnsureCreatedAsync();
```
`EnsureCreatedAsync` 会创建数据库(如果不存在),并会创建所有表,但不会应用迁移脚本,适用于开发环境。
## 使用代码将最新迁移脚本应用到数据库
```csharp
await dbContext.Database.MigrateAsync();
```
`MigrateAsync` 会应用迁移脚本,如果数据库不存在,会先创建数据库,适用于生产环境。
## 注意事项
如果先前已经使用 `EnsureCreatedAsync` 创建了数据库,再使用 `MigrateAsync` 会抛出异常,因为数据库和所有表已经存在,因为迁移中有创建表的操作脚本。
如果数据库不存在,使用 `MigrateAsync` 会先创建数据库,再创建迁移历史表,然后再应用迁移脚本,但这个过程中会打印一些错误警告信息,虽然可以忽略,但不够优雅。
## 优雅的做法
这里使用到一些 EF Core 的内部服务,注意他们在实现细节上的区别。
```csharp
private static async ValueTask RunMigrationAsync(TDbContext dbContext, CancellationToken cancellationToken)
{
var strategy = dbContext.Database.CreateExecutionStrategy();
var dbCreator = dbContext.GetService<IRelationalDatabaseCreator>();
var historyRepository = dbContext.GetService<IHistoryRepository>();
await strategy.ExecuteAsync(async () =>
{
if (!await dbCreator.ExistsAsync(cancellationToken))
{
await dbCreator.CreateAsync(cancellationToken);
}
await historyRepository.CreateIfNotExistsAsync();
await dbContext.Database.MigrateAsync(cancellationToken);
});
}
```
1、使用 `dbCreator.ExistsAsync` 先检查数据库是否存在,不存在则仅仅创建数据库(它不会创建表),这个时候得到一个空的数据库。
2、再使用 `historyRepository.CreateIfNotExistsAsync` 在空数据库中创建迁移历史表,用于保存迁移记录。
3、最后使用 `dbContext.Database.MigrateAsync` 应用迁移脚本,因为迁移脚本中的操作会在空数据库中执行,包括创建表等等操作。
4、迁移完成后数据库中会有所有表和迁移历史表迁移历史表中登记了最新的迁移记录。
5、后续再使用 `dbContext.Database.MigrateAsync` 会检查迁移历史表,如果有新的迁移脚本,会应用新的迁移脚本。
6、这样就实现了自动迁移和数据库初始化。
为了以上操作都在一个事务中执行,我们使用 `dbContext.Database.CreateExecutionStrategy` 创建一个执行策略, 在策略中执行上述操作。
## 数据库初始化的方式
在最新的 EF 9.0 建议使用 UseSeeding 和 UseAsyncSeeding 并利用初始数据对数据库进行种子设定,这些代码会在`迁移`时执行。
```csharp
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)=> optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFDataSeeding;Trusted_Connection=True;ConnectRetryCount=0")
.UseSeeding((context, _) =>
{
var testBlog = context.Set<Blog>().FirstOrDefault(b => b.Url == "http://test.com");
if (testBlog == null)
{
context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
context.SaveChanges();
}
})
.UseAsyncSeeding(async (context, _, cancellationToken) =>
{
var testBlog = await context.Set<Blog>().FirstOrDefaultAsync(b => b.Url == "http://test.com", cancellationToken);
if (testBlog == null)
{
context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
await context.SaveChangesAsync(cancellationToken);
}
});
```
不推荐传统的种子数据方式,因为它会在每次迁移时都会执行,而且不支持异步操作。
```csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().HasData(
new Blog { BlogId = 1, Url = "http://sample.com" },
new Blog { BlogId = 2, Url = "http://sample2.com" }
);
}
```
## 零度框架的数据库初始化
在某些场景下数据库初始化可能是一个耗时的操作,我们使用 IHostedService 或 BackgroundService 来异步执行数据库初始化。
```csharp
public class DbInitializer : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public DbInitializer(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// The code to initialize the database
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
```

View File

@ -1,4 +1,4 @@
# 使用 TimeProvider 服务
# 使用 TimeProvider 类注入时间
System.TimeProvider 是一种时间抽象,它以 DateTimeOffset 类型的形式提供时间点。 通过使用 TimeProvider可确保代码可测试且可预测。 TimeProvider 已在 .NET 8 中引入。
@ -22,6 +22,7 @@ public class CustomTimeProvider: TimeProvider
```csharp
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddSingleton<TimeProvider, CustomTimeProvider>();
```
```csharp

View File

@ -12,7 +12,7 @@ Add Usertest/test
[http://192.168.0.202:15672](http://192.168.0.202:15672)
[http://mqtt.yuangan:15672](http://mqtt.yuangan:15672)
[http://mqtt.test:15672](http://mqtt.test:15672)
# RabbitMQ Management MQTT

View File

@ -1,60 +0,0 @@
# 物联网云平台功能说明
## 设备管理
### 产品列表
产品列表可以对产品进行管理,包括产品的创建、查看、编辑、删除等操作,产品属性的定义、查看、编辑、删除等操作,产品的协议定义、查看、编辑、删除等操作。
### 设备列表
可以对设备进行管理,包括设备的创建、查看、编辑、删除等操作,包括设备属性绑定、查看、编辑、删除等操作,设备的协议绑定、查看、编辑、删除等操作。
### 设备分组
## 数据统计
### 实时数据
查看设备的实时数据,包括设备的属性数据、事件数据、报警数据等,可根据产品、设备、时间等条件进行筛选查看。
### 历史数据
可通过曲线图和列表方式查看设备的历史数据,可根据产品、设备、时间、属性等条件进行筛选查看,支持历史数据导出。
### 统计报表
统计报表可对历史数据进行统计分析,通过年月日时等维度进行统计,支持统计报表导出,可根据产品、设备、时间、属性等条件进行筛选查看。
## 数据可视化
可通过地图、曲线图、仪表盘、表格等方式对数据进行可视化展示,可根据产品、设备、时间等条件进行筛选查看。
## 视频监控
支持摄像头接入,提供视频监控功能,可查看现场视频。
## 项目展示
针对不同项目提供工艺流程、设备状态、报警信息、数据统计等展示,可根据项目进行展示配置。
## 系统管理
### 组织架构
支持组织架构管理,包括组织的创建、查看、编辑、删除等操作,支持组织的用户管理、角色管理、权限管理等操作。
### 用户管理
支持用户的创建、查看、编辑、删除等操作,支持用户的角色管理、权限管理等操作。
### 角色管理
支持角色的创建、查看、编辑、删除等操作,支持角色的权限管理等操作。
## 第三方接入
支持第三方接入,包括设备接入、数据接入、应用接入等操作。