Skip to content

Commit 869272a

Browse files
GitHubView 1.0.3 (#10)
* fix(user): 首选 “TryGetValue” 调用,而不是由 “ContainsKey” 检查保护的字典索引器访问,以避免双重查找 * fix(null): 修复一大堆 null 警告 * fix(labels): 分页获取 fix [BUG]: 无法获取全部内容 #9 * chore(git): 不跟踪调试配置文件 * fix(ctb&label): 要 超 速 了 !!!!! - 如果达到速率限制 - 如果获取到速率重置时间戳 - 等待直到速率重置 - 获取不到速率重置时间戳 或 - 其他错误 => ERROR 失败 chore(IDE): 设置 IDE0008 - 用显式类型代替 var 为警告等级 * chore(IDE): 从 git 版本控制中移除警告配置
1 parent dee22f3 commit 869272a

File tree

7 files changed

+334
-74
lines changed

7 files changed

+334
-74
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,4 +360,8 @@ MigrationBackup/
360360
.ionide/
361361

362362
# Fody - auto-generated XML schema
363-
FodyWeavers.xsd
363+
FodyWeavers.xsd
364+
365+
# Debug Config
366+
launchSettings.json
367+
.editorconfig

ghv/Command/About.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ internal class About
66
{
77
public static void Show()
88
{
9-
var table = new Table();
9+
Table table = new();
1010
table.AddColumn("条目");
1111
table.AddColumn("内容");
1212
string versionLink = Program.Version == "develop"

ghv/Command/Contribute.cs

Lines changed: 198 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static async Task ExecuteAsync(string owner, string repo, int topN)
1717
{
1818
repo = GetUserInput("请输入仓库名称:", ValidateOwnerOrRepo);
1919
}
20-
20+
2121
if (topN < 1)
2222
{
2323
topN = GetTopNInput("请输入要获取前几个贡献者信息:");
@@ -26,7 +26,6 @@ public static async Task ExecuteAsync(string owner, string repo, int topN)
2626
bool repositoryExists = await ValidateRepositoryExists(owner, repo);
2727
if (!repositoryExists)
2828
{
29-
AnsiConsole.Markup("[red]该仓库不存在,请检查输入的所有者和仓库名是否正确。[/]\n");
3029
return;
3130
}
3231

@@ -36,27 +35,160 @@ public static async Task ExecuteAsync(string owner, string repo, int topN)
3635
private static async Task GetContributorsAsync(string owner, string repo, int topN)
3736
{
3837
using HttpClient client = new();
39-
string url = $"https://api.github.com/repos/{owner}/{repo}/contributors?per_page={topN}";
4038
client.DefaultRequestHeaders.Add("User-Agent", "CSharpApp");
41-
HttpResponseMessage response = await client.GetAsync(url);
39+
int pages = (int)Math.Ceiling((double)topN / 100); // 提前声明总页数,每页个数在后面计算
40+
try
41+
{
42+
if (topN <= 100) // 100个以内不需要分页
43+
{
44+
pages = 1; // 1 页就够 - 不用再请求总页数
45+
}
46+
else // 100个以上则先定义每页100个以减少页数,进而减少请求次数
47+
{
48+
HttpResponseMessage response = await client.GetAsync("https://api.github.com/repos/{owner}/{repo}/contributors"); // 先请求一下获取总页数
49+
if (response.IsSuccessStatusCode)
50+
{
51+
string? linkHeader = response.Headers.Contains("Link") ? response.Headers.GetValues("Link").FirstOrDefault() : null;
52+
if (linkHeader == null)
53+
{
54+
// 解析失败
55+
AnsiConsole.Markup("[yellow]无法解析总分页数: Link 头为 null[/]\n");
56+
}
57+
else
58+
{
59+
Regex regex = new(@"<([^>]+)>;\s*rel=""last""");
60+
Match match = regex.Match(linkHeader);
61+
string allPagesString = match.Groups[1].Value; // 返回匹配的 URL 部分
4262

43-
if (response.IsSuccessStatusCode)
63+
if (match.Success)
64+
{
65+
if (int.TryParse(allPagesString, out int allPages))
66+
{
67+
// 成功解析,allPages 现在是整数
68+
/*
69+
首先将 topN 强制转换为 double 类型,以确保执行除法时获得浮点数。
70+
然后通过 Math.Ceiling() 向上取整。
71+
最后将结果转换回 int 类型获得最终需要请求的总页数。
72+
*/
73+
if (pages > allPages)
74+
{
75+
pages = allPages; // 再多也不能超出总页数呀
76+
}
77+
}
78+
else
79+
{
80+
// 解析失败
81+
AnsiConsole.Markup("[yellow]无法解析总分页数: 匹配失败[/]\n");
82+
}
83+
}
84+
}
85+
}
86+
}
87+
}
88+
catch (HttpRequestException e)
4489
{
45-
string content = await response.Content.ReadAsStringAsync();
46-
JsonNode jsonNode = JsonNode.Parse(content);
47-
JsonArray contributors = jsonNode.AsArray();
90+
AnsiConsole.Markup($"[yellow]无法解析总分页数: 请求失败[/]\n[yellow]信息:{e.Message}[/]");
91+
// 直接用最开始算的,后面还有错再报,这里用警告样式
92+
}
93+
94+
#if DEBUG
95+
AnsiConsole.Markup($"[purple][[DEBUG]] 请求分页数: {pages}[/]\n");
96+
#endif
97+
98+
List<JsonNode> allContributors = [];
99+
string url;
100+
101+
for (int i = 1; i <= pages; i++)
102+
{
103+
// 计算此页个数
104+
if ((i == pages) && (topN % 100 != 0)) // 如果是最后一页
105+
{
106+
url = $"https://api.github.com/repos/{owner}/{repo}/contributors?per_page={topN % 100}&page={i}";
107+
}
108+
else
109+
{
110+
url = $"https://api.github.com/repos/{owner}/{repo}/contributors?per_page={100}&page={i}";
111+
}
112+
113+
#if DEBUG
114+
AnsiConsole.Markup($"[purple][[DEBUG]] 请求: {url}[/]\n");
115+
#endif
116+
117+
try
118+
{
119+
HttpResponseMessage response = await client.GetAsync(url);
120+
if (response.IsSuccessStatusCode)
121+
{
122+
string jsonResponse = await response.Content.ReadAsStringAsync();
123+
JsonNode? jsonNode = JsonNode.Parse(jsonResponse);
124+
if (jsonNode == null || jsonNode.AsArray() == null)
125+
{
126+
AnsiConsole.Markup("[red]无法解析贡献者数据: 请求成功,但返回 null[/]\n");
127+
return;
128+
}
129+
130+
// 合并获取到的贡献者
131+
allContributors.AddRange(jsonNode.AsArray().Cast<JsonNode>());
132+
}
133+
else if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
134+
{
135+
string? resetTime = response.Headers.Contains("X-RateLimit-Reset")
136+
? response.Headers.GetValues("X-RateLimit-Reset").FirstOrDefault() // 获取第一个值
137+
: "未知";
138+
139+
AnsiConsole.Markup($"[yellow]你 太 快 了 ![/]\n[yellow]您的请求已达到 [link=https://docs.github.com/zh/rest/using-the-rest-api/rate-limits-for-the-rest-api]GitHub API 速率限制[/][/]\n");
140+
if ((resetTime != null) && (resetTime != "未知"))
141+
{
142+
// 等待速率限制重置
143+
#if DEBUG
144+
AnsiConsole.Markup($"[purple][[DEBUG]] 速率重置时间戳: {Markup.Escape(resetTime)}[/]\n");
145+
#endif
146+
long resetTimestamp = long.Parse(resetTime);
147+
DateTime resetDateTimeUtc = DateTimeOffset.FromUnixTimeSeconds(resetTimestamp).DateTime;
148+
// 将 UTC 时间转换为本地时间
149+
DateTime resetDateTimeLocal = resetDateTimeUtc.ToLocalTime();
150+
#if DEBUG
151+
AnsiConsole.Markup($"[purple][[DEBUG]] 转化后速率重置时间: {resetDateTimeLocal}[/]\n");
152+
#endif
153+
TimeSpan waitTime = resetDateTimeLocal - DateTime.Now;
154+
AnsiConsole.Markup($"[yellow]重置时间: {resetDateTimeLocal} (需要等待 {waitTime} )[/]");
155+
Thread.Sleep(waitTime); // 等待重置
156+
i--; // 本次请求没成功,后面重试时再请求这页
157+
}
158+
else
159+
{
160+
AnsiConsole.Markup("[red]无法获取贡献数据: 请求达到速率限制,但无法获取重置时间戳: X-RateLimit-Reset 头为 null 或无法转为 String 类型[/]\n");
161+
return;
162+
}
163+
}
164+
else
165+
{
48166

49-
var table = new Table();
167+
AnsiConsole.Markup("[red]无法获取贡献数据: 请求失败: 未捕获异常[/]\n");
168+
return;
169+
}
170+
}
171+
catch (Exception ex)
172+
{
173+
// 捕获网络或其他错误
174+
AnsiConsole.Markup($"[red]无法获取贡献数据: 请求发生异常: {Markup.Escape(ex.Message)}[/]\n");
175+
}
176+
177+
Thread.Sleep(1000); // 请求间间隔 1 秒 - https://docs.github.com/zh/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2022-11-28#handle-rate-limit-errors-appropriately
178+
}
179+
180+
if (allContributors.Count > 0)
181+
{
182+
Table table = new();
50183
table.AddColumn("排名");
51184
table.AddColumn("贡献者");
52185
table.AddColumn("贡献计数");
53186

54187
int rank = 1;
55-
foreach (JsonNode contributor in contributors)
188+
foreach (JsonNode contributor in allContributors)
56189
{
57-
JsonObject contributorObject = contributor.AsObject();
58-
string login = Markup.Escape(contributorObject["login"].GetValue<string>()); // 转义 [] 之类的特殊符号
59-
int contributions = contributorObject["contributions"].GetValue<int>();
190+
string login = Markup.Escape(contributor["login"]?.ToString() ?? string.Empty);
191+
int contributions = contributor["contributions"]?.GetValue<int>() ?? 0;
60192
table.AddRow(rank.ToString(), $"[link=https://github.com/{login}]{login}[/]", contributions.ToString());
61193
rank++;
62194
}
@@ -65,7 +197,7 @@ private static async Task GetContributorsAsync(string owner, string repo, int to
65197
}
66198
else
67199
{
68-
AnsiConsole.Markup("[red]获取贡献者数据失败。[/]");
200+
AnsiConsole.Markup("[red]该仓库没有贡献者 (如果仓库为空则可能出现此情况)。[/]");
69201
}
70202
}
71203

@@ -95,12 +227,33 @@ private static int GetTopNInput(string prompt)
95227
AnsiConsole.Markup("[cyan]? [/]" + prompt + " ");
96228
string input = (Console.ReadLine() ?? string.Empty).Trim();
97229

98-
if (int.TryParse(input, out topN) && topN >= 1)
230+
// 尝试解析输入为 int 类型
231+
if (int.TryParse(input, out topN))
99232
{
100-
break;
233+
// 检查输入的数字是否在 int 类型的有效范围内
234+
if (topN >= 1)
235+
{
236+
break;
237+
}
238+
else
239+
{
240+
AnsiConsole.Markup("[red]请输入一个大于等于 1 的正整数。[/]\n");
241+
}
242+
}
243+
else
244+
{
245+
// 如果 int.TryParse 失败,说明输入不符合 int 类型的格式
246+
// 检查是否超出 int 范围
247+
if (input.Length > 10 || long.TryParse(input, out long largeInput) && largeInput > int.MaxValue)
248+
{
249+
AnsiConsole.Markup("[red]太多了!int 类型最大为 2,147,483,647[/]\n");
250+
}
251+
else
252+
{
253+
// 普通的格式错误提示
254+
AnsiConsole.Markup("[red]请输入一个有效的正整数。[/]\n");
255+
}
101256
}
102-
103-
AnsiConsole.Markup("[red]请输入一个大于等于 1 的正整数。[/]\n");
104257
}
105258
while (true);
106259

@@ -116,11 +269,33 @@ private static bool ValidateOwnerOrRepo(string input)
116269

117270
private static async Task<bool> ValidateRepositoryExists(string owner, string repo)
118271
{
119-
using HttpClient client = new();
120-
string url = $"https://api.github.com/repos/{owner}/{repo}";
121-
client.DefaultRequestHeaders.Add("User-Agent", "CSharpApp");
122-
HttpResponseMessage response = await client.GetAsync(url);
123-
return response.IsSuccessStatusCode;
272+
try
273+
{
274+
using HttpClient client = new();
275+
string url = $"https://api.github.com/repos/{owner}/{repo}";
276+
client.DefaultRequestHeaders.Add("User-Agent", "CSharpApp");
277+
HttpResponseMessage response = await client.GetAsync(url);
278+
279+
if (!response.IsSuccessStatusCode)
280+
{
281+
// 仓库不存在的提示
282+
AnsiConsole.Markup("[red]仓库不存在(或没有访问权限),请检查输入的所有者和仓库名是否正确。[/]\n");
283+
return false;
284+
}
285+
return true;
286+
}
287+
catch (HttpRequestException e)
288+
{
289+
// 处理速率限制或其他访问错误
290+
AnsiConsole.Markup($"[red]无法验证仓库是否存在: 请求错误[/]\n[red]您的请求可能已达到 [link=https://docs.github.com/zh/rest/using-the-rest-api/rate-limits-for-the-rest-api]GitHub API 速率限制[/][/]\n[red]详细错误信息: {e}[/]\n");
291+
return false;
292+
}
293+
catch (Exception ex)
294+
{
295+
// 其他异常处理
296+
AnsiConsole.Markup($"[red]无法验证仓库是否存在: {ex.Message}[/]\n");
297+
return false;
298+
}
124299
}
125300
}
126301
}

0 commit comments

Comments
 (0)