# Git 提交分析报告 **提交哈希**: 12f0caafc735aae8dc9c8d19f2c0829288106280 **提交时间**: 2026-05-25 01:24:18 +0800 **作者**: lincube \ **提交信息**: fix.继续修复 .NET运行时问题 --- ## 变更统计 - **修改文件数**: 3 - **新增行数**: 181 - **删除行数**: 16 - **净变更行数**: +165 ### 变更文件 | 文件 | 变更类型 | 变更行数 | |------|---------|---------| | LanMountainDesktop.Launcher/Services/DotNetRuntimeProbe.cs | 核心修复 | +80 / -15 | | LanMountainDesktop.Tests/DotNetRuntimeProbeTests.cs | 新增测试 | +109 | | LanMountainDesktop/installer/LanMountainDesktop.iss | 增强检测 | +8 | --- ## 详细变更分析 ### 1. LanMountainDesktop.Launcher/Services/DotNetRuntimeProbe.cs **核心问题修复**: 支持按用户安装的 .NET 运行时检测 #### 变更 1: 扩展选项配置 ```csharp public record DotNetRuntimeProbeOptions { // ... existing properties ... + public string? LocalAppDataPath { get; init; } } ``` - **新增**: `LocalAppDataPath` 配置项 - **用途**: 支持检测 `%LOCALAPPDATA%\dotnet` 目录下的运行时 #### 变更 2: 定义必需的共享框架 ```csharp public const string RequiredSharedFrameworkName = "Microsoft.NETCore.App"; + public const string WindowsDesktopSharedFrameworkName = "Microsoft.WindowsDesktop.App"; + + private static readonly string[] RequiredSharedFrameworkNames = + [ + RequiredSharedFrameworkName, + WindowsDesktopSharedFrameworkName + ]; ``` - **新增**: Windows Desktop 运行时框架名称常量 - **变更**: 将单一框架改为框架列表,支持多框架检测 #### 变更 3: 核心检测逻辑重构 ```csharp public static DotNetRuntimeProbeResult Probe(DotNetRuntimeProbeOptions? options = null) { // ... 初始化代码 ... + var localAppDataRoot = GetLocalAppDataPath(options); + var perUserDotnetRoot = !string.IsNullOrWhiteSpace(localAppDataRoot) + ? Path.Combine(localAppDataRoot, "dotnet") + : null; + + foreach (var frameworkName in RequiredSharedFrameworkNames) + { + foreach (var basePath in EnumerateDotNetInstallRoots(options)) + { + var sharedFrameworkDirectory = Path.Combine(basePath, "shared", frameworkName); + searchedPaths.Add(sharedFrameworkDirectory); + var isPerUser = perUserDotnetRoot is not null && + string.Equals(basePath, perUserDotnetRoot, StringComparison.OrdinalIgnoreCase); + AddDirectoryRuntimes(sharedFrameworkDirectory, frameworkName, + isPerUser ? "shared-framework-directory-per-user" : "shared-framework-directory", + detected); + } + } } ``` - **核心改进**: - 支持扫描多个 .NET 安装位置 - 区分系统安装和按用户安装 - 检测多个必需的共享框架 #### 变更 4: 新增安装根目录枚举 ```csharp + private static IEnumerable EnumerateDotNetInstallRoots(DotNetRuntimeProbeOptions options) + { + var programFilesRoot = options.Architecture == DotNetRuntimeArchitecture.X86 + ? GetProgramFilesX86Path(options) + : GetProgramFilesPath(options); + + yield return Path.Combine(programFilesRoot, "dotnet"); + + var localAppData = GetLocalAppDataPath(options); + if (!string.IsNullOrWhiteSpace(localAppData)) + { + var perUserDotnet = Path.Combine(localAppData, "dotnet"); + if (!string.Equals(perUserDotnet, Path.Combine(programFilesRoot, "dotnet"), StringComparison.OrdinalIgnoreCase)) + { + yield return perUserDotnet; + } + } + } ``` - **功能**: 枚举所有可能的 .NET 安装根目录 - **包括**: - Program Files 下的系统安装 - LocalAppData 下的按用户安装 #### 变更 5: 增强 dotnet host 路径搜索 ```csharp + var localAppData = GetLocalAppDataPath(options); + if (!string.IsNullOrWhiteSpace(localAppData)) + { + var perUserHost = Path.Combine(localAppData, "dotnet", OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"); + if (!string.Equals(perUserHost, Path.Combine(programFilesRoot, "dotnet", OperatingSystem.IsWindows() ? "dotnet.exe" : "dotnet"), StringComparison.OrdinalIgnoreCase)) + { + yield return perUserHost; + } + } ``` - **改进**: 在按用户路径中搜索 dotnet host 可执行文件 #### 变更 6: 添加 LocalAppData 路径获取 ```csharp + private static string GetLocalAppDataPath(DotNetRuntimeProbeOptions options) + { + if (!string.IsNullOrWhiteSpace(options.LocalAppDataPath)) + { + return Path.GetFullPath(options.LocalAppDataPath); + } + + return Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + } ``` - **功能**: 获取 LocalAppData 路径,支持自定义配置 #### 变更 7: 增强 dotnet CLI 检测 ```csharp - private static void AddDotNetCliRuntimes( - string? dotNetHostPath, - string sharedFrameworkName, - List detected) + private static void AddDotNetCliRuntimes( + string? dotNetHostPath, + List detected) ``` - **变更**: 移除 `sharedFrameworkName` 参数 - **改进**: 使用 `RequiredSharedFrameworkNames` 列表,支持多框架检测 --- ### 2. LanMountainDesktop.Tests/DotNetRuntimeProbeTests.cs **新增单元测试**: 6 个全面的测试用例 #### 测试 1: 按用户运行时检测 ```csharp [Fact] public void Probe_DetectsPerUserRuntime() { CreateRuntime(_localAppData, "10.0.5", DotNetRuntimeProbe.RequiredSharedFrameworkName); var result = DotNetRuntimeProbe.Probe( CreateOptions(DotNetRuntimeArchitecture.X64)); Assert.True(result.IsAvailable); Assert.Contains(result.DetectedRuntimes, runtime => runtime.Version == "10.0.5" && runtime.Source == "shared-framework-directory-per-user"); } ``` #### 测试 2: Windows Desktop 运行时检测 ```csharp [Fact] public void Probe_DetectsWindowsDesktopRuntime() { CreateRuntime(_programFiles, "10.0.5", DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName); var result = DotNetRuntimeProbe.Probe( CreateOptions(DotNetRuntimeArchitecture.X64)); Assert.False(result.IsAvailable); Assert.Contains(result.DetectedRuntimes, runtime => runtime.Name == DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName && runtime.Version == "10.0.5"); } ``` #### 测试 3: 按用户 Windows Desktop 运行时检测 ```csharp [Fact] public void Probe_DetectsPerUserWindowsDesktopRuntime() { CreateRuntime(_localAppData, "10.0.5", DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName); var result = DotNetRuntimeProbe.Probe( CreateOptions(DotNetRuntimeArchitecture.X64)); Assert.Contains(result.DetectedRuntimes, runtime => runtime.Name == DotNetRuntimeProbe.WindowsDesktopSharedFrameworkName && runtime.Version == "10.0.5" && runtime.Source == "shared-framework-directory-per-user"); } ``` #### 测试 4: 在按用户路径中查找 dotnet host ```csharp [Fact] public void Probe_FindsDotNetHost_InPerUserPath() { var dotnetDir = Path.Combine(_localAppData, "dotnet"); Directory.CreateDirectory(dotnetDir); File.WriteAllText(Path.Combine(dotnetDir, "dotnet.exe"), string.Empty); var result = DotNetRuntimeProbe.Probe(new DotNetRuntimeProbeOptions { // ... 配置 ... LocalAppDataPath = _localAppData, IncludeRegistry = false, IncludeDotNetCli = false }); Assert.NotNull(result.DotNetHostPath); Assert.Contains("LocalAppData", result.DotNetHostPath); } ``` #### 测试 5: 优先使用 Program Files host ```csharp [Fact] public void Probe_PrefersProgramFilesHost_OverPerUserHost() { // 创建两个 dotnet.exe:一个在 Program Files,一个在 LocalAppData var result = DotNetRuntimeProbe.Probe(/* ... */); Assert.NotNull(result.DotNetHostPath); Assert.Contains("ProgramFiles", result.DotNetHostPath); } ``` #### 测试 6: 合并系统和按用户运行时 ```csharp [Fact] public void Probe_CombinesSystemAndPerUserRuntimes() { CreateRuntime(_programFiles, "10.0.5"); CreateRuntime(_localAppData, "10.0.3"); var result = DotNetRuntimeProbe.Probe(CreateOptions( DotNetRuntimeArchitecture.X64)); Assert.True(result.IsAvailable); Assert.Contains(result.DetectedRuntimes, runtime => runtime.Version == "10.0.5"); Assert.Contains(result.DetectedRuntimes, runtime => runtime.Version == "10.0.3"); } ``` **测试覆盖评估**: - ✅ 按时用户安装检测 - ✅ Windows Desktop 运行时检测 - ✅ 混合安装场景 - ✅ host 路径优先级 - ⚠️ 建议:添加跨架构检测测试 --- ### 3. LanMountainDesktop/installer/LanMountainDesktop.iss **增强安装程序检测逻辑**: #### 新增函数 ```pascal function GetPerUserDotNetDesktopRuntimePath(): String; begin Result := ExpandConstant('{localappdata}\dotnet\shared\Microsoft.WindowsDesktop.App'); end; ``` #### 增强检测函数 ```pascal function IsDotNetDesktopRuntimeInstalled(): Boolean; begin - Result := IsDotNet10RuntimePresent(GetTargetDotNetDesktopRuntimePath()); + Result := IsDotNet10RuntimePresent(GetTargetDotNetDesktopRuntimePath()) or + IsDotNet10RuntimePresent(GetPerUserDotNetDesktopRuntimePath()); end; ``` **变更说明**: - 支持检测按用户安装的 .NET Desktop 运行时 - 允许在缺少系统安装但有按用户安装时继续安装 --- ## 问题分析与解决方案 ### 原始问题 ❌ **问题**: 只能检测系统级别的 .NET 运行时安装 ❌ **问题**: 无法识别用户通过 Visual Studio 或 winget 安装的 .NET 运行时 ❌ **问题**: 可能导致安装程序要求用户重新安装 .NET 运行时,即使已存在按用户安装 ### 解决方案 ✅ **扩展搜索路径**: - Program Files (系统级别) - %LOCALAPPDATA% (用户级别) ✅ **支持多框架检测**: - Microsoft.NETCore.App - Microsoft.WindowsDesktop.App ✅ **智能合并**: - 合并系统和用户安装的运行时 - 优先使用系统级别的 dotnet host --- ## 代码审查要点 ### 潜在问题 1. **性能考虑**: - ✅ 良好:使用 `yield return` 延迟枚举,避免不必要的文件系统访问 - ⚠️ 低风险:路径比较使用 `OrdinalIgnoreCase`,性能影响可接受 2. **路径安全性**: - ✅ 良好:使用 `Path.GetFullPath()` 规范化路径 - ✅ 良好:避免路径注入攻击 3. **错误处理**: - ✅ 良好:`string.IsNullOrWhiteSpace()` 检查空值 - ✅ 良好:可选的配置参数,提供默认值 4. **测试覆盖**: - ✅ 优秀:6 个新的测试用例覆盖主要场景 - ⚠️ 建议:添加边界情况测试(如路径包含特殊字符) ### 代码质量评估 | 方面 | 评分 | 说明 | |------|------|------| | 架构设计 | ⭐⭐⭐⭐⭐ | 清晰的分层和职责分离 | | 代码可读性 | ⭐⭐⭐⭐⭐ | 良好的命名和注释 | | 测试覆盖 | ⭐⭐⭐⭐⭐ | 全面的测试用例 | | 错误处理 | ⭐⭐⭐⭐ | 考虑周全,可进一步增强 | | 性能 | ⭐⭐⭐⭐⭐ | 延迟枚举优化性能 | --- ## 影响范围 ### 功能影响 - ✅ **运行时检测**: 支持按用户安装的 .NET 运行时 - ✅ **安装程序**: 更智能的 .NET 运行时检测 - ✅ **用户体验**: 减少不必要的 .NET 运行时重新安装 ### 技术影响 - ✅ **跨用户支持**: 支持同一机器上的多个用户配置 - ✅ **混合安装**: 支持系统和按用户安装混合场景 - ✅ **向后兼容**: 保持对现有系统安装的检测能力 --- ## 安全考虑 1. **路径验证**: ✅ 使用 `Path.GetFullPath()` 防止路径注入 2. **权限检查**: ✅ 区分系统和用户目录的访问权限 3. **文件存在性**: ✅ 在访问前检查文件和目录存在性 --- ## 总结 这是一次高质量的功能修复提交,主要解决了 .NET 运行时检测的关键问题: ### 核心改进 1. ✅ **扩展检测范围**: 支持按用户安装的 .NET 运行时 2. ✅ **多框架支持**: 同时检测 Core 和 Desktop 运行时 3. ✅ **智能合并**: 正确处理系统和用户安装的混合场景 4. ✅ **全面测试**: 6 个新的单元测试确保可靠性 5. ✅ **安装程序增强**: Inno Setup 脚本同步更新 ### 代码质量 - 🏆 **优秀**: 架构清晰,代码规范 - 🏆 **优秀**: 测试覆盖全面 - 🏆 **优秀**: 错误处理周全 **建议**: ✅ 可以合并,建议后续添加更多边界情况测试。