mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
408 lines
12 KiB
Markdown
408 lines
12 KiB
Markdown
# Git 提交分析报告
|
||
|
||
**提交哈希**: 12f0caafc735aae8dc9c8d19f2c0829288106280
|
||
**提交时间**: 2026-05-25 01:24:18 +0800
|
||
**作者**: lincube \<lincube3@hotmail.com\>
|
||
**提交信息**: 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<string> 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<DotNetRuntimeInfo> detected)
|
||
+ private static void AddDotNetCliRuntimes(
|
||
+ string? dotNetHostPath,
|
||
+ List<DotNetRuntimeInfo> 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 脚本同步更新
|
||
|
||
### 代码质量
|
||
|
||
- 🏆 **优秀**: 架构清晰,代码规范
|
||
- 🏆 **优秀**: 测试覆盖全面
|
||
- 🏆 **优秀**: 错误处理周全
|
||
|
||
**建议**: ✅ 可以合并,建议后续添加更多边界情况测试。
|