Skip to content

Conversation

whitecat346
Copy link
Member

Refactor/Netwrok

概述

这个PR重构了网络类的实现,提供了两种方式以进行网络通信

修改

  • 删除原来的Net
  • 添加INetServiceNetService
  • 添加StaticNet
  • 添加HttpClientPool
  • 添加对应的README文档

使用方法

(抄的README)

INetService和NetService

概述

INetService 是一个基于依赖注入模式的 HTTP 客户端服务实现,提供了丰富的 HTTP 请求功能,包括发送普通请求、发送 JSON 数据以及处理响应。

使用说明

构造 NetService 实例

NetService 需要通过依赖注入来使用。

INetService netService = new NetService("MyHttpClient", "https://api.example.com", "my-token");
发送 HTTP 请求
  1. 发送无内容的请求:
var response = await netService.SendAsync(HttpMethod.Get, "https://api.example.com/resource");
  1. 发送带内容的请求:
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.example.com/resource")
{
    Content = new StringContent("{\"key\":\"value\"}", Encoding.UTF8, "application/json")
};
var response = await netService.SendAsync(request);
  1. 发送 JSON 数据并获取反序列化结果:
var result = await netService.SendJsonAsync<MyResponseType, MyRequestType>(HttpMethod.Post, "https://api.example.com/resource", new MyRequestType { Key = "value" });

注意事项

  1. 依赖注入:推荐使用依赖注入来管理 NetService 实例。
  2. 令牌管理:确保传递正确的令牌以进行身份验证。
  3. 异常处理:捕获可能的网络异常,例如 HttpRequestException
  4. 线程安全:NetService 的方法是线程安全的,可以在多线程环境中使用。

StaticNet

概述

StaticNet 是一个静态类,提供了简单易用的 HTTP 客户端服务功能,适合快速实现 HTTP 请求。

使用说明

发送 HTTP 请求
  1. 发送无内容的请求:
var response = await StaticNet.SendAsync(HttpMethod.Get, new Uri("https://api.example.com/resource"));
  1. 发送带内容的请求:
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.example.com/resource")
{
    Content = new StringContent("{\"key\":\"value\"}", Encoding.UTF8, "application/json")
};
var response = await StaticNet.SendAsync(request);
  1. 发送 JSON 数据并获取反序列化结果:
var result = await StaticNet.SendJsonAsync<MyResponseType, MyRequestType>(HttpMethod.Post, new Uri("https://api.example.com/resource"), new MyRequestType { Key = "value" });

注意事项

  1. 静态类:StaticNet 是静态类,无需通过依赖注入管理。
  2. 令牌管理:确保传递正确的令牌以进行身份验证。
  3. 异常处理:捕获可能的网络异常,例如 HttpRequestException
  4. 线程安全:StaticNet 的方法是线程安全的,可以在多线程环境中使用。

@whitecat346 whitecat346 self-assigned this Jul 15, 2025
@whitecat346 whitecat346 added 新功能 包括了新功能的更改 优化 对现有内容的优化与改进 labels Jul 15, 2025
Copy link
Collaborator

@lhx077 lhx077 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这次 PR 主要做了一次 Microsoft 账户认证相关的网络层重构,移除了大量旧的 Result 泛型包装和 Net 工具类,用了新的 NetService/StaticNet,并加了一套 HttpClient 池,各种接口签名也都直接返回异常了,代码风格有明显改变。整体来说,重构思路没问题,接口简洁了不少,网络层抽象也更加清晰,但细节上还是有不少地方值得推敲和改进。我细看了一下所有改动,下面我就像个老工程师一样(比较唠叨的那种)),絮絮叨叨地把发现的问题给你捋一遍。

首先,NetService 和 StaticNet 的设计思路是对的,但你们的 HttpClient 池实现其实没太大必要,这里管理 HttpClient 的生命周期反而容易引发资源泄漏和并发问题。比如 HttpClientPool 里用 Timer 定时清理,InUse 标记,实际并没有保证线程安全,Dispose 也只是简单地设置 InUse=false,没有真正释放资源。建议如果没有特殊需求,可以直接用 DI 生命周期托管 HttpClient,或者用 IHttpClientFactory,这样就不用自己造轮子了。自己管理 HttpClient 容易踩坑,尤其是池里一旦有异常或并发场景,Timer 清理不及时,容易造成连接泄漏。

其次,异常的处理方式变了,Result 包装全都去掉,直接用异常传播。这看起来更 .NET 风格,但后续业务调用方必须写 try-catch,不然异常直接冒到上层,整个流程会中断,体验不太友好。比如 AccountService 里 RefreshMicrosoftAccountAsync,现在 catch 的粒度很粗,只有最外层,内部 MicrosoftAuthService 的所有网络异常都直接抛出来了。建议还是能在底层做一层异常包裹,或者加个错误码、错误对象,这样调用方能更细致地判断错误类型,比如网络错误、认证失败、token 过期等等,否则只能靠异常类型去分辨,业务逻辑不好展开。

DeviceFlow 相关的 State 类也有变化,原来是普通 class,现在直接继承 Exception。这个做法不太妥当,业务状态和异常最好分开,DeviceFlowDeclined、Expired 等本质上是认证流程状态,不应该用 Exception 表达。建议还是保持原来的 State 类型,异常只用来表示真正的错误,而不是流程分支,否则未来流程判断时,只能靠 try-catch,代码可读性和维护性都下降。

接口签名统一了,IMicrosoftAuthService 几乎所有方法都去掉了泛型 Result 包装,直接返回业务对象或抛异常。这个没错,但 Result 类其实有用,比如 PollForTokenAsync 轮询时,各种状态码(declined、expired、pending 等)直接用异常抛出,调用方只能靠 catch 来分辨,失去了原来 Result.Error 的细粒度处理能力,而且如果后续要支持更多状态(比如为未来的 OAuth 扩展),会很难扩展。

网络层的 StaticNet/NetService 封装了大量日志,但有点过于冗余,比如每次请求都记录类型、内容、响应等,实际业务场景下,日志量太大会影响性能。建议日志级别可配置,或者只在错误、慢请求时打详细日志,正常请求只保留最必要的信息,否则生产环境容易刷屏,重要日志反而淹没。

OAuthData 里的 URL 拼接方式有改动,原来是 "authorize"、"token" 等直接拼成,现在加了 "/" 前缀,这个要格外小心。如果 OAuth2BaseUri 末尾已经有 "/",拼接时会不会多出 "//",需要测试下,否则有兼容性隐患。另外 FormUrlReqData 里的字典构建方式也有变化,要注意序列化时的 null 和空字符串,确保不会影响实际请求。

OpenBrowserAsync 方法改了 UseShellExecute=false,原来是 true,这个可能影响 Linux/Mac 上的兼容性,需要多平台测试下,尤其是在非 Windows 环境下,Process.Start 的行为和权限不一样,可能直接导致认证流程无法弹窗。

AccountService 里缓存、状态管理没怎么动,但注意 _isLoaded、_cachedAccounts 这些字段在多线程场景下是否安全,尤其是 DI 后可能有多个 Service 实例,最好加锁或者用 Concurrent 集合。

还有一些小细节,比如 BoolToOnlineStatusConverter 的 Convert 方法签名变了(加了 nullable),这点没问题,但要保证 XAML 绑定时不会有类型不匹配异常。

总的来说,这次 PR 做了不少清理和重构,代码简洁了,但异常处理、HttpClient 池、业务状态表达和日志方面都还有提升空间。建议后续可以:

  1. 网络层直接用 DI 的 HttpClient,或者 IHttpClientFactory,自己造池子反而容易踩坑。
  2. 异常和业务状态分离,DeviceFlow 相关不要全用 Exception,Result 类其实很有用,建议保留。
  3. 日志要做级别和策略控制,别全量输出,生产环境容易刷屏。
  4. OAuth2 URL 拼接和表单参数要多做兼容性测试,尤其是和第三方服务对接时。
  5. OpenBrowserAsync 要多平台测试,尤其是非 Windows 环境。
  6. AccountService 的缓存和状态管理要考虑线程安全。
  7. 最好能补一套单元测试,覆盖各种异常和边界情况,确保新网络层在各种场景下都能正常工作。

最后,这次改动比较大,建议合并前多做回归测试,尤其是 Microsoft 认证的完整流程和各种异常分支,避免上线后出现细碎问题。整体思路是对的,但细节上还得继续打磨,别因为重构而丢了原有的健壮性。希望这些建议能帮到你们,代码写得越来越好!

@lhx077 lhx077 removed the request for review from DotnetInstall July 17, 2025 08:57
@whitecat346
Copy link
Member Author

首先,NetService 和 StaticNet 的设计思路是对的,但你们的 HttpClient 池实现其实没太大必要,这里管理 HttpClient 的生命周期反而容易引发资源泄漏和并发问题。比如 HttpClientPool 里用 Timer 定时清理,InUse 标记,实际并没有保证线程安全,Dispose 也只是简单地设置 InUse=false,没有真正释放资源。建议如果没有特殊需求,可以直接用 DI 生命周期托管 HttpClient,或者用 IHttpClientFactory,这样就不用自己造轮子了。自己管理 HttpClient 容易踩坑,尤其是池里一旦有异常或并发场景,Timer 清理不及时,容易造成连接泄漏。

这个前期想的就是用IHttpClientFactory实现的,但是写道一半发现遇到死胡同写不动,部分东西无法实现,你帮忙改一下吧,我是搞不定……

其次,异常的处理方式变了,Result 包装全都去掉,直接用异常传播。这看起来更 .NET 风格,但后续业务调用方必须写 try-catch,不然异常直接冒到上层,整个流程会中断,体验不太友好。比如 AccountService 里 RefreshMicrosoftAccountAsync,现在 catch 的粒度很粗,只有最外层,内部 MicrosoftAuthService 的所有网络异常都直接抛出来了。建议还是能在底层做一层异常包裹,或者加个错误码、错误对象,这样调用方能更细致地判断错误类型,比如网络错误、认证失败、token 过期等等,否则只能靠异常类型去分辨,业务逻辑不好展开。

接口签名统一了,IMicrosoftAuthService 几乎所有方法都去掉了泛型 Result 包装,直接返回业务对象或抛异常。这个没错,但 Result 类其实有用,比如 PollForTokenAsync 轮询时,各种状态码(declined、expired、pending 等)直接用异常抛出,调用方只能靠 catch 来分辨,失去了原来 Result.Error 的细粒度处理能力,而且如果后续要支持更多状态(比如为未来的 OAuth 扩展),会很难扩展。

听你这么一说,要不然还是变成原来的Result

DeviceFlow 相关的 State 类也有变化,原来是普通 class,现在直接继承 Exception。这个做法不太妥当,业务状态和异常最好分开,DeviceFlowDeclined、Expired 等本质上是认证流程状态,不应该用 Exception 表达。建议还是保持原来的 State 类型,异常只用来表示真正的错误,而不是流程分支,否则未来流程判断时,只能靠 try-catch,代码可读性和维护性都下降。

直接继承Exception的原因是那个虽然是表示状态的类,但是他们走的OnErrorIObserveable返回通道,只能用Exception,没办法

网络层的 StaticNet/NetService 封装了大量日志,但有点过于冗余,比如每次请求都记录类型、内容、响应等,实际业务场景下,日志量太大会影响性能。建议日志级别可配置,或者只在错误、慢请求时打详细日志,正常请求只保留最必要的信息,否则生产环境容易刷屏,重要日志反而淹没。

会改

AccountService 里缓存、状态管理没怎么动,但注意 _isLoaded、_cachedAccounts 这些字段在多线程场景下是否安全,尤其是 DI 后可能有多个 Service 实例,最好加锁或者用 Concurrent 集合。

你写的欸,怎么丢到我头上搞了……

希望这些建议能帮到你们,代码写得越来越好!

真是老爷爷啊(划掉)

@lhx077
Copy link
Collaborator

lhx077 commented Jul 17, 2025

改成result还是算了,既然这样能跑就先这样吧,要不然改来改去太麻烦
IHttpClientFactory那个后面的话我有时间试试吧,这两天忙的不可开交,后面再说
账户系统你不也重构了嘛(大粉猪生气倒过来了.jpg)

@whitecat346
Copy link
Member Author

那这个PR我就先Draft了,你先帮忙处理一下,依赖这个的相关计划也推一下。我去干别的了(例如UI)

@whitecat346 whitecat346 marked this pull request as draft July 17, 2025 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
优化 对现有内容的优化与改进 新功能 包括了新功能的更改
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants