我们在日常产品维护时,往往会遇到底层基础框架需要升级的情况,尤其是当底层框架升级到一个新的大版本时,可能会带来一些不兼容的变更,这时候我们就需要做好充分的准备工作,以确保升级过程顺利进行。从本文开始,我们将详细讲解如何将我们的产品从 .NET 8 升级到 .NET 10 。在正式升级之前,我们需要对项目的依赖关系进行全面梳理,包括第三方库的版本兼容性、NuGet 包的升级情况以及项目中使用的一些已废弃的 API 是否在新版本中被移除。与此同时,我们还需要制定一份详细的升级计划,明确升级的步骤、时间节点以及回滚方案,以便在升级过程中出现问题时能够快速恢复到原有状态。只有做好这些准备工作,才能最大程度地降低升级风险,确保项目在升级后能够正常运行。
一、确认环境
在升级之前,首先需要确认当前的开发环境是否满足 .NET 10 的要求。我们需要检查以下几个方面:
-
操作系统:确保开发环境的操作系统版本支持 .NET 10,以下是最低操作系统版本和推荐操作系统版本。
平台 最低支持版本 推荐版本 Windows Windows 10 (1607) / Server 2016 Windows 11 / Server 2022+ macOS macOS 12 (Monterey) macOS 15 (Sequoia)+ Linux Ubuntu 20.04 / RHEL 8.10 / Debian 11 最新 LTS 版本 (如 Ubuntu 24.04) -
开发工具:确保使用的开发工具已经更新到支持 .NET 10 的版本。建议使用最新版本的 Visual Studio 2022 或更高版本,以获得最佳的开发体验和兼容性。
-
.NET SDK :安装 .NET 10 SDK,并确保环境变量配置正确。可以通过命令行运行
dotnet --version来验证安装是否成功。 -
安装 dotnet-outdated-tool:这是一个非常有用的工具,可以帮助我们检查项目中使用的 NuGet 包是否有更新版本,是否存在过时的包,特别是那些可能不兼容 .NET 10 的包。安装命令如下:
shelldotnet tool install --global dotnet-outdated-tool
以上这些环境确认步骤是升级前必须做的基础准备工作,确保开发环境与 .NET 10 的要求相匹配,可以避免在升级过程中遇到不必要的环境兼容性问题。
二、升级步骤
在这一小节,我们将详细讲解升级步骤,为后续的升级做准备。
2.1 基础框架升级
框架升级涉及到 10个 .csproj ,10个 Dockerfile 文件的修改,以及一些基础设施相关的配置文件的调整。我们需要将所有项目的目标框架(Target Framework)从 net8.0 修改为 net10.0,并且升级所有相关的 NuGet 包到支持 .NET 10 的版本。在这个过程中,我们可能会遇到一些破坏性变更(Breaking Changes),需要仔细阅读 .NET 10 的发布说明,了解哪些 API 已经被废弃或移除,并且根据需要修改代码以适应新的框架版本。下面这个检查列表可以帮助我们系统地完成升级:
- 将所有
.csproj中<TargetFramework>net8.0</TargetFramework>改为net10.0(10 个服务可并行修改) - 更新所有
Dockerfile基础镜像:mcr.microsoft.com/dotnet/sdk:8.0→mcr.microsoft.com/dotnet/sdk:10.0mcr.microsoft.com/dotnet/aspnet:8.0→mcr.microsoft.com/dotnet/aspnet:10.0
- 移除
SP.Common/SP.Common.csproj中严重过时的Microsoft.AspNetCore.Http.Abstractions 2.2.0(2018 年 ASP.NET Core 2.2 遗留包,.NET 10 框架已内置) - 从各服务
.csproj中移除以下在net10.0框架内已内置的包:System.Text.Json(出现在 SP.Common 等多个服务)System.Text.Encodings.Web(出现在 IdentityService、FinanceService、ConfigService、NotificationService、ReportService 服务中)
2.2 微软官方包批量升级
这是一个低风险步骤,我们只需要将下表中包的版本升级到支持 .NET 10 的最新版本即可。可以使用 dotnet-outdated 工具来自动检查和升级这些包,确保它们与 .NET 10 兼容。
| 包名 | 当前版本 | 目标版本 | 涉及服务数 |
|---|---|---|---|
Microsoft.EntityFrameworkCore.*(所有子包) |
8.0.8 | 10.0.x | 8 个 |
Microsoft.AspNetCore.Identity.EntityFrameworkCore |
8.0.8 | 10.0.x | 6 个 |
Microsoft.AspNetCore.Authentication.JwtBearer |
8.0.8 | 10.0.x | ConfigService、IdentityService |
Microsoft.Extensions.*(所有 Abstractions) |
8.0.x | 10.0.x | SP.Common |
System.Formats.Asn1 |
8.0.1 | 10.0.x 或移除 | SP.Gateway |
Microsoft.VisualStudio.Web.CodeGeneration.Design |
8.0.7 | 10.0.x | 6 个(开发工具) |
Microsoft.Build |
17.8.43 | 对齐 SDK 版本 | 6 个(开发工具) |
同样,列出检查列表:
- 批量执行包版本升级
-
Microsoft.EntityFrameworkCore.*(所有子包) -
Microsoft.AspNetCore.Identity.EntityFrameworkCore -
Microsoft.AspNetCore.Authentication.JwtBearer -
Microsoft.Extensions.*(所有 Abstractions) -
System.Formats.Asn1(升级到支持 .NET 10 的版本,或根据 .NET 10 的更新情况考虑移除) -
Microsoft.VisualStudio.Web.CodeGeneration.Design(升级到支持 .NET 10 的版本) -
Microsoft.Build(升级到与 .NET 10 SDK 版本对齐的版本)
2.3 EF Core + Pomelo MySQL
这一步属于中风险操作,我们要做的是将Pomelo.EntityFrameworkCore.MySql 8.0.3 包升级到 10.0.x 版本,以确保它与 EF Core 10 和 .NET 10 兼容,这个操作一共涉及八个微服务。在升级完后,我们还需要验证每个微服务的EF Core 的迁移历史是否能够正常应用到 MySQL 数据库中,确保数据访问层在升级后能够正常工作。以下是检查列表:
- EF Core 官方包随 Phase 2 一同升到
10.0.x - Pomelo.EntityFrameworkCore.MySql 8.0.3 → 升到
10.0.x- Pomelo 版本号跟随 EF Core 而非 MySQL Server 版本
- 在 NuGet 上确认
10.0.x可用 - 涉及 8 个服务
- 验证各服务 EF Core 迁移历史(EF 8 → 10 的迁移文件本身通常无需修改
Tip:Pomelo 版本号跟随 EF Core 的版本号而非 MySQL Server 版本号
2.4 认证与安全包
这一步也是中风险操作,我们需要升级 OpenIddict 包到支持 .NET 10 的版本,以确保认证和授权功能在升级后能够正常工作。OpenIddict 是我们项目中用于实现身份认证和授权的核心组件,升级过程中需要特别注意它与 ASP.NET Core 10 的兼容性,以及可能引入的破坏性变更。以下是检查列表:
-
OpenIddict 7.0.0 → 升级到最新版,确认
net10.0TFM 支持 -
涉及 4 个包,出现在 SP.Gateway 和 SP.IdentityService:
包名 出现位置 OpenIddict.AspNetCoreIdentityService OpenIddict.EntityFrameworkCoreIdentityService OpenIddict.Validation.AspNetCoreGateway、IdentityService OpenIddict.Validation.ServerIntegrationGateway -
System.IdentityModel.Tokens.Jwt 8.12.1 → 升到最新版,确认与新版 OpenIddict 兼容(SP.Common)
-
Azure.Identity 1.11.4 → 升到最新版(SP.Gateway)
2.5 API 文档迁移 --- Swashbuckle → OpenAPI + Scalar
这一步的风险不大,但是工作量巨大。我们需要将所有微服务中使用的 Swashbuckle 包替换为 OpenAPI 和 Scalar 相关的包,以适应 .NET 10 的新特性和最佳实践。这个操作涉及到 API 文档生成和维护的核心部分,需要确保在替换过程中 API 文档能够正确生成,并且与现有的 API 定义保持一致。
1. 所有服务
这里是针对八个服务都需要升级的包,我们需要将以下 Swashbuckle 包替换为 OpenAPI 和 Scalar 相关的包,并且修改 Program.cs 中的 API 文档配置代码,以适应新的包和 .NET 10 的特性。以下是检查列表:
每个服务 .csproj:
- 移除
Swashbuckle.AspNetCore 6.6.2 - 添加
Scalar.AspNetCore(NuGet 包)
每个服务 Program.cs:
-
移除
AddSwaggerGen(c => { ... })整个配置块(含 XML 注释、Bearer 安全配置) -
移除
app.UseSwagger()和app.UseSwaggerUI() -
添加基于 .NET 10 OpenAPI 的新配置:
csharp// 注册 OpenAPI(含 JWT Bearer 安全方案) builder.Services.AddOpenApi(options => { options.AddDocumentTransformer((document, context, ct) => { document.Info = new() { Title = "服务名称 API", Version = "v1" }; return Task.CompletedTask; }); options.AddOperationTransformer((operation, context, ct) => { // JWT Bearer 安全配置 operation.Security = [new OpenApiSecurityRequirement { [new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }] = [] }]; return Task.CompletedTask; }); }); // 仅 Development/Local 环境暴露文档 UI if (app.Environment.IsDevelopment() || app.Environment.EnvironmentName == "Local") { app.MapOpenApi(); app.MapScalarApiReference(); // 访问路径:/scalar/v1 }
IdentityService 额外注意 :
SwaggerTokenRequestFilter的逻辑需评估是否迁移为IOpenApiOperationTransformer
2. SP.Gateway
网关服务中有些包需要单独的升级,并且 API 文档的聚合方案需要重新设计。由于我们之前使用 Swashbuckle 和 SwaggerForOcelot 来实现 API 文档的聚合展示,现在需要评估新的方案来替代它们,以适应 .NET 10 的新特性和最佳实践。以下是检查列表:
.csproj:
- 移除
Swashbuckle.AspNetCore 6.6.2和MMLib.SwaggerForOcelot 8.3.2 - 添加
Scalar.AspNetCore
Program.cs:
- 移除
AddSwaggerGen()+AddSwaggerForOcelot()及其 UI 配置块 - 移除
app.UseSwaggerForOcelotUI(...)调用
Gateway 层文档聚合方案(二选一):
| 方案 | 说明 |
|---|---|
| 方案 A(推荐) | 各下游服务通过 Ocelot 路由暴露自身 /openapi/v1.json,Gateway Scalar UI 配置多端点,分服务展示 |
| 方案 B | 评估是否有支持 OpenAPI 的 SwaggerForOcelot 替代库 |
- 同步清理 Nacos 中的
SwaggerConfigDataId 配置
2.6 API 网关 Ocelot 升级
这是一个高风险的步骤,我们需要将 Ocelot 包升级到支持 .NET 10 的版本,以确保 API 网关能够正常工作。Ocelot 是我们项目中用于实现 API 网关功能的核心组件,升级过程中需要特别注意它与 ASP.NET Core 10 的兼容性,以及可能引入的破坏性变更。以下是检查列表:
- Ocelot 24.0.1 在 NuGet 确认支持
net10.0或net9.0的最新版本 - 若最新版仅有
net9.0TFM,.NET 10 可向下兼容引用,需验证功能正常 - 升级后检查
ocelot.json路由配置是否有破坏性变更
2.7 Baidu.AI OCR 替换 为 DeepSeek OCR 或其他 OCR 方案
进行这一步的目的是,由于Baidu.AI OCR SDK 目前不支持 .NET10,因此我们需要将其替换为 DeepSeek OCR ,以确保智能账单录入功能能够在升级后继续正常工作。
1. 移除 Baidu.AI
首先,我们要移除 Baidu.AI OCR SDK 相关的代码和配置,这包括:
-
.csproj中移除Baidu.AI 4.15.16 - 删除或重命名
BaiduOCROptions.cs→DeepSeekOCROptions.cs - 移除
OCRConsumerService中的Ocr _client(Baidu.Aip.Ocr.Ocr)字段及初始化逻辑 - 移除 Nacos/appsettings 中的
BaiduOCR配置节
2. 新增 DeepSeek OCR 实现
-
新建
DeepSeekOCROptions:csharppublic class DeepSeekOCROptions { public string ApiKey { get; set; } = ""; public string Endpoint { get; set; } = "https://api.deepseek.com"; public string Model { get; set; } = "deepseek-chat"; // 需支持视觉的模型 } -
修改
OCRServiceExtensions.cs:csharppublic static IServiceCollection AddOCRService( this IServiceCollection services, IConfiguration configuration, string sectionName = "DeepSeekOCR") { services.Configure<DeepSeekOCROptions>(configuration.GetSection(sectionName)); services.AddScoped<IOCRService, DeepSeekOCRServiceImpl>(); // 接口实现类无需改动 return services; }IOCRService接口和BaiduOCRServiceImpl(仅负责发 RabbitMQ 消息)无需改动 -
修改
OCRConsumerService.cs-
构造函数注入
IHttpClientFactory(替换Ocr _client) -
替换 Baidu API 调用代码块为 DeepSeek Vision API 调用:
csharp// 图片字节 → base64 string base64Image = Convert.ToBase64String(image); string mimeType = "image/jpeg"; // 根据实际 ContentType 确定 // 构造 DeepSeek Vision 请求 var requestBody = new { model = _options.Model, messages = new[] { new { role = "user", content = new object[] { new { type = "image_url", image_url = new { url = $"data:{mimeType};base64,{base64Image}" } }, new { type = "text", text = "请识别图片中的所有文字,只输出文字内容,不要添加任何解释。" } } } } }; var response = await httpClient.PostAsJsonAsync("/chat/completions", requestBody, stoppingToken); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: stoppingToken); string recognizedText = result .GetProperty("choices")[0] .GetProperty("message") .GetProperty("content") .GetString() ?? ""; -
更新 appsettings 添加
DeepSeekOCR配置节:json"DeepSeekOCR": { "ApiKey": "", "Endpoint": "https://api.deepseek.com", "Model": "deepseek-chat" }
-
2.8 SP.MLService 服务中的 Microsoft.ML 升级到 .NET 10
这是一个高风险的步骤,我们需要将 SP.MLService 服务中使用的 Microsoft.ML 包升级到支持 .NET 10 的版本,以确保机器学习相关功能能够在升级后继续正常工作。Microsoft.ML 是我们项目中用于实现机器学习功能的核心组件,升级过程中需要特别注意它与 .NET 10 的兼容性,以及可能引入的破坏性变更。它被MLContext、LightGbmRankingTrainer、Text Featurization Pipeline 使用,核心文件是CategoryMatcher.cs。以下是检查列表:
- 确认 NuGet 上
Microsoft.ML和Microsoft.ML.LightGbm是否有支持net10.0或net9.0的版本- 若有 → 直接升级,在 Docker(Linux)和 Windows 两个平台测试原生库(LightGBM native binaries)加载
- 若仅
net9.0→ 以兼容方式引用(.NET 10 向下兼容net9包)
- 升级
Microsoft.ML和Microsoft.ML.LightGbm到对应最新版 - 升级
MongoDB.Driver 3.5.0到最新版(FeedbackStorage 依赖) - 运行
CategoryMatcher功能验证:训练 → 预测,确认排名结果正确
2.9 其余第三方包
最后,我们需要对项目中使用的其他第三方包进行升级,以确保它们与 .NET 10 兼容。这些包可能不如前面提到的核心包那样关键,但同样需要升级以避免潜在的兼容性问题,但是这一步属于低风险操作。以下是升级列表:
| 包 | 当前版本 | 风险 | 说明 |
|---|---|---|---|
Serilog |
4.2.0 | 🟢 低 | 多 TFM,升最新版 |
Serilog.AspNetCore |
8.0.1 | 🟢 低 | 升最新版 |
Serilog.Sinks.Grafana.Loki |
8.3.0 | 🟢 低 | 升最新版 |
AutoMapper |
14.0.0 | 🟢 低 | 多 TFM |
Quartz |
3.14.0 | 🟢 低 | 确认 net10 TFM |
Quartz.AspNetCore |
3.14.0 | 🟢 低 | 同上 |
RabbitMQ.Client |
7.1.2 | 🟢 低 | netstandard,兼容 |
StackExchange.Redis |
2.7.33 | 🟢 低 | netstandard,兼容 |
Refit |
8.0.0 | 🟡 中 | 确认最新版 TFM |
Refit.HttpClientFactory |
8.0.0 | 🟡 中 | 同上 |
SixLabors.ImageSharp |
3.1.11 | 🟢 低 | 多 TFM |
SixLabors.Fonts |
2.1.3 | 🟢 低 | 多 TFM |
SixLabors.ImageSharp.Drawing |
2.0.2 | 🟢 低 | 多 TFM |
MongoDB.Driver |
3.5.0 | 🟢 低 | 多 TFM |
RestSharp |
112.1.0 | 🟢 低 | 多 TFM |
Minio |
6.0.4 | 🟡 中 | 确认最新版 net10 支持 |
Twilio |
7.13.0 | 🟢 低 | 多 TFM |
Microsoft.Data.SqlClient |
5.1.3 | 🟢 低 | 升最新版 |
2.10 破坏性变更代码审查
在完成上述升级步骤后,我们需要进行一次全面的代码审查,重点关注可能存在的破坏性变更。这包括但不限于以下几个方面:
-
EF Core 8 升级 10 Breaking Changes
- 查阅官方文档
- 重点关注:查询行为、Owned Entity、JSON 列映射变化
- 逐一核对各服务 DbContext 和 LINQ 查询写法
-
ASP.NET Core Breaking Changes
- 查阅 .NET 9 迁移文档
- 查阅 .NET 10 迁移文档
- 重点关注:中间件管道、路由、Minimal API 变更
-
OpenIddict 配置 API 变更
- 对照新版 OpenIddict Changelog 检查
SP.IdentityService/Program.cs和SP.Gateway/Program.cs中的AddOpenIddict()配置链
- 对照新版 OpenIddict Changelog 检查
-
SwaggerTokenRequestFilter迁移评估- 文件:
SP.IdentityService/SwaggerTokenRequestFilter.cs - 评估其 Token 请求逻辑是否需要迁移为
IOpenApiOperationTransformer(.NET 10 OpenAPI 扩展点)
- 文件:
2.11 验证
升级完成后,我们需要进行全面的验证,确保项目在升级后能够正常运行,并且性能没有明显下降。这包括:
-
dotnet build每个项目,确认零错误(顺序:SP.Common→ 其余 8 个服务 →SP.Gateway) -
dotnet ef migrations list验证各有迁移的服务历史状态完整 - Docker Compose 启动,验证所有服务健康检查通过
- 访问各服务
/scalar/v1,确认 Scalar UI 正常加载且 JWT Bearer 认证操作可用 - 完整 AuthN 流程:Token 申请 → 刷新 → JwtBearer 验证
- OCR 端对端测试:上传图片 → RabbitMQ 触发 → DeepSeek 识别 → DB 写入 → 查询识别结果
- SP.MLService 分类预测:模型加载/训练 → 预测接口返回正确结果
- 服务间 Refit HTTP 调用链联调测试
2.12 风险汇总
在这里,我们总结一下升级过程中可能遇到的风险点,在升级的过程中要格外注意:
| 风险等级 | 包 / 领域 | 说明 |
|---|---|---|
| 🔴 高(已决策) | Swashbuckle + MMLib.SwaggerForOcelot | 迁移到 OpenAPI + Scalar,需修改所有服务 Program.cs |
| 🔴 高(已决策) | Baidu.AI | 替换为 DeepSeek Vision API,OCRConsumerService 需重写 |
| 🔴 高(随主线) | Microsoft.ML / LightGbm | 原生库 .NET 10 兼容性需验证,LightGBM native binaries 需测试 |
| 🟡 中 | OpenIddict | 版本升级后配置 API 可能有变化 |
| 🟡 中 | Ocelot | 需确认 net10 或 net9 版本可用 |
| 🟡 中 | Pomelo EFCore MySQL | EF Core 10 对应版本需确认 NuGet 可用 |
| 🟡 中 | Refit | 需确认最新版 TFM 支持 |
| 🟢 低 | Microsoft.* 官方包 | 版本号随 .NET 升级,低风险 |
| 🟢 低 | Serilog / AutoMapper / Quartz 等 | 多 TFM 库,向下兼容 |
三、总结
本篇文章我们详细介绍了将项目从 .NET 8 升级到 .NET 10 的准备工作和升级步骤。升级过程中,我们需要对项目的依赖关系进行全面梳理,制定详细的升级计划,并且按照步骤逐一完成基础框架升级、核心包升级、API 文档迁移、API 网关升级以及 OCR 方案替换等关键操作。在升级过程中,我们还需要特别注意可能存在的破坏性变更,并且在升级完成后进行全面的验证,确保项目能够正常运行并且性能没有明显下降。通过这次升级,我们不仅能够享受到 .NET 10 带来的性能优化和新特性,还能够为后续的 AI 智能化改造打下坚实的基础。