-
Notifications
You must be signed in to change notification settings - Fork 449
完善社区#276的提议(by qoder) #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: net10
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| using Microsoft.Extensions.DependencyInjection; | ||
| using System; | ||
|
|
||
| namespace WebApiClientCore.Extensions.OAuths.DependencyInjection | ||
| { | ||
| /// <summary> | ||
| /// OAuthToken配置的依赖注入扩展 | ||
| /// </summary> | ||
| public static class OAuthTokenOptionsExtensions | ||
| { | ||
| /// <summary> | ||
| /// 配置OAuth Token刷新选项 | ||
| /// 使用独立的配置选项,支持从appsettings.json加载 | ||
| /// </summary> | ||
| /// <param name="builder">HttpClient构建器</param> | ||
| /// <param name="configure">配置委托</param> | ||
| /// <returns>HttpClient构建器</returns> | ||
| public static IHttpClientBuilder ConfigureOAuthTokenOptions( | ||
| this IHttpClientBuilder builder, | ||
| Action<OAuthTokenOptions> configure) | ||
| { | ||
| builder.Services.Configure<OAuthTokenOptions>(builder.Name, configure); | ||
| return builder; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// 配置OAuth Token刷新选项(通过IConfiguration) | ||
| /// 支持从appsettings.json等配置源加载 | ||
| /// </summary> | ||
| /// <param name="builder">HttpClient构建器</param> | ||
| /// <param name="configuration">配置节</param> | ||
| /// <returns>HttpClient构建器</returns> | ||
| public static IHttpClientBuilder ConfigureOAuthTokenOptions( | ||
| this IHttpClientBuilder builder, | ||
| Microsoft.Extensions.Configuration.IConfiguration configuration) | ||
| { | ||
| builder.Services.Configure<OAuthTokenOptions>(builder.Name, configuration); | ||
| return builder; | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| using System; | ||
|
|
||
| namespace WebApiClientCore.Extensions.OAuths | ||
| { | ||
| /// <summary> | ||
| /// HttpApiOptions的扩展方法 | ||
| /// </summary> | ||
| public static class HttpApiOptionsExtensions | ||
| { | ||
| /// <summary> | ||
| /// OAuthTokenOptions在Properties字典中的键 | ||
| /// </summary> | ||
| private const string TokenRefreshOptionsKey = "WebApiClientCore.OAuths.TokenRefreshOptions"; | ||
|
|
||
| /// <summary> | ||
| /// 获取或设置OAuth Token刷新选项 | ||
| /// 此方法使用HttpApiOptions的Properties字典存储配置,不污染核心配置类 | ||
| /// </summary> | ||
| /// <param name="options">HttpApi选项</param> | ||
| /// <returns>OAuth Token刷新选项</returns> | ||
| public static OAuthTokenOptions GetOAuthTokenOptions(this HttpApiOptions options) | ||
| { | ||
| if (!options.Properties.TryGetValue(TokenRefreshOptionsKey, out var value)) | ||
| { | ||
| value = new OAuthTokenOptions(); | ||
| options.Properties[TokenRefreshOptionsKey] = value; | ||
| } | ||
| return (OAuthTokenOptions)value; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// 配置OAuth Token刷新选项 | ||
| /// 此方法使用HttpApiOptions的Properties字典存储配置,不污染核心配置类 | ||
| /// </summary> | ||
| /// <param name="options">HttpApi选项</param> | ||
| /// <param name="configure">配置委托</param> | ||
| /// <returns>HttpApi选项</returns> | ||
| public static HttpApiOptions ConfigureOAuthToken(this HttpApiOptions options, Action<OAuthTokenOptions> configure) | ||
| { | ||
| var tokenOptions = options.GetOAuthTokenOptions(); | ||
| configure(tokenOptions); | ||
| return options; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| namespace WebApiClientCore.Extensions.OAuths | ||
| { | ||
| /// <summary> | ||
| /// 表示OAuth Token的刷新选项 | ||
| /// </summary> | ||
| public class OAuthTokenOptions | ||
| { | ||
| /// <summary> | ||
| /// 获取或设置是否启用Token提前刷新窗口 | ||
| /// 默认值为 true | ||
| /// </summary> | ||
| public bool UseTokenRefreshWindow { get; set; } = true; | ||
|
|
||
| /// <summary> | ||
| /// 获取或设置固定刷新窗口时长(秒) | ||
| /// 当剩余有效时间小于等于此值时,触发提前刷新 | ||
| /// 默认值为 60 秒 | ||
| /// </summary> | ||
| public int RefreshWindowSeconds { get; set; } = 60; | ||
|
|
||
| /// <summary> | ||
| /// 获取或设置刷新窗口百分比(0-1) | ||
| /// 当剩余有效时间小于等于总有效期的此百分比时,触发提前刷新 | ||
| /// 默认值为 0.1 (10%) | ||
| /// </summary> | ||
| public double RefreshWindowPercentage { get; set; } = 0.1; | ||
|
|
||
| /// <summary> | ||
| /// 获取或设置刷新窗口计算策略 | ||
| /// 默认值为 Auto (自动选择固定时长和百分比中的较小值) | ||
| /// </summary> | ||
| public RefreshWindowStrategy RefreshWindowStrategy { get; set; } = RefreshWindowStrategy.Auto; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| namespace WebApiClientCore.Extensions.OAuths | ||
| { | ||
| /// <summary> | ||
| /// 表示Token刷新窗口计算策略 | ||
| /// </summary> | ||
| public enum RefreshWindowStrategy | ||
| { | ||
| /// <summary> | ||
| /// 仅使用固定秒数 | ||
| /// </summary> | ||
| FixedSeconds = 0, | ||
|
|
||
| /// <summary> | ||
| /// 仅使用百分比 | ||
| /// </summary> | ||
| Percentage = 1, | ||
|
|
||
| /// <summary> | ||
| /// 自动选择:取固定秒数和百分比的较小值 | ||
| /// </summary> | ||
| Auto = 2 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -75,7 +75,7 @@ public async Task<TokenResult> GetTokenAsync() | |||||||||||||||||
| using var scope = this.services.CreateScope(); | ||||||||||||||||||
| this.token = await this.RequestTokenAsync(scope.ServiceProvider).ConfigureAwait(false); | ||||||||||||||||||
| } | ||||||||||||||||||
| else if (this.token.IsExpired() == true) | ||||||||||||||||||
| else if (this.ShouldRefreshToken(this.token)) | ||||||||||||||||||
| { | ||||||||||||||||||
| using var scope = this.services.CreateScope(); | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -88,6 +88,96 @@ public async Task<TokenResult> GetTokenAsync() | |||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// <summary> | ||||||||||||||||||
| /// 判断是否应该刷新Token | ||||||||||||||||||
| /// 混合方案:优先从独立配置读取,其次从 HttpApiOptions.Properties 读取,最后使用默认行为 | ||||||||||||||||||
| /// </summary> | ||||||||||||||||||
| /// <param name="token">token结果</param> | ||||||||||||||||||
| /// <returns></returns> | ||||||||||||||||||
| private bool ShouldRefreshToken(TokenResult token) | ||||||||||||||||||
|
||||||||||||||||||
| { | ||||||||||||||||||
| // 获取配置 | ||||||||||||||||||
| var tokenOptions = this.GetTokenRefreshOptions(); | ||||||||||||||||||
|
|
||||||||||||||||||
| if (!tokenOptions.UseTokenRefreshWindow) | ||||||||||||||||||
| { | ||||||||||||||||||
| // 如果禁用刷新窗口,使用原有行为 | ||||||||||||||||||
| return token.IsExpired(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // 计算刷新窗口 | ||||||||||||||||||
| var refreshWindow = this.CalculateRefreshWindow(token, tokenOptions); | ||||||||||||||||||
| return token.IsExpired(refreshWindow); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /// <summary> | ||||||||||||||||||
| /// 获取Token刷新配置 | ||||||||||||||||||
| /// 优先级:独立配置(IOptionsMonitor<OAuthTokenOptions>) > HttpApiOptions.Properties > 默认配置 | ||||||||||||||||||
| /// </summary> | ||||||||||||||||||
| /// <returns>Token刷新配置</returns> | ||||||||||||||||||
| private OAuthTokenOptions GetTokenRefreshOptions() | ||||||||||||||||||
| { | ||||||||||||||||||
| // 优先从独立配置读取 (方案2:独立配置类) | ||||||||||||||||||
| var oauthOptionsMonitor = this.services.GetService<IOptionsMonitor<OAuthTokenOptions>>(); | ||||||||||||||||||
| if (oauthOptionsMonitor != null) | ||||||||||||||||||
| { | ||||||||||||||||||
| try | ||||||||||||||||||
| { | ||||||||||||||||||
| var options = oauthOptionsMonitor.Get(this.Name); | ||||||||||||||||||
| // 检查是否为默认配置,如果不是则使用 | ||||||||||||||||||
| if (options != null) | ||||||||||||||||||
| { | ||||||||||||||||||
| return options; | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+127
to
+131
|
||||||||||||||||||
| // 检查是否为默认配置,如果不是则使用 | |
| if (options != null) | |
| { | |
| return options; | |
| } | |
| // 直接返回 options,无需检查 null(IOptionsMonitor.Get 永不返回 null) | |
| return options; |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty catch blocks on lines 133 and 151 silently suppress all exceptions, making debugging difficult. Consider at least logging the exception or catching specific exception types that are expected. If any exception is acceptable here, add a comment explaining why it's safe to ignore.
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The null check on line 146 is redundant. The IOptionsMonitor<T>.Get(string name) method never returns null - it creates a new default instance if no configuration exists. This check should be removed.
| if (httpApiOptions != null) | |
| { | |
| return httpApiOptions.GetOAuthTokenOptions(); | |
| } | |
| return httpApiOptions.GetOAuthTokenOptions(); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty catch block silently suppresses all exceptions, making debugging difficult. Consider at least logging the exception or catching specific exception types that are expected. If any exception is acceptable here, add a comment explaining why it's safe to ignore.
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Local scope variable 'token' shadows TokenProvider.token.
| private TimeSpan CalculateRefreshWindow(TokenResult token, OAuthTokenOptions options) | |
| { | |
| var fixedWindow = TimeSpan.FromSeconds(options.RefreshWindowSeconds); | |
| var percentageWindow = TimeSpan.FromSeconds(token.Expires_in * options.RefreshWindowPercentage); | |
| private TimeSpan CalculateRefreshWindow(TokenResult tokenResult, OAuthTokenOptions options) | |
| { | |
| var fixedWindow = TimeSpan.FromSeconds(options.RefreshWindowSeconds); | |
| var percentageWindow = TimeSpan.FromSeconds(tokenResult.Expires_in * options.RefreshWindowPercentage); |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new token refresh window feature lacks test coverage for key scenarios:
- Tests for
ShouldRefreshTokenmethod - Tests for
GetTokenRefreshOptionsmethod's configuration priority logic - Tests for
CalculateRefreshWindowmethod with different strategies - Integration tests verifying the configuration from both
HttpApiOptionsandIOptionsMonitor<OAuthTokenOptions>
Consider adding tests that verify the configuration fallback chain and the refresh window calculation strategies work correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing
using Microsoft.Extensions.Options;import. The code callsbuilder.Services.Configure<OAuthTokenOptions>on lines 22 and 37, which is an extension method from theMicrosoft.Extensions.Optionsnamespace. Without this import, the code will not compile.