0%

用 Go 调用 GitHub API 搜索 Issues

项目结构

项目目录大概长这样:

1
2
3
4
5
study_go/
├── go.mod
├── main.go
└── pkg/
└── github.go

其中 pkg/github.go 里封装了 API 调用和数据处理逻辑,main.go 则是入口文件。


封装 GitHub API

我们先写一个 SearchIssue 方法,请求 GitHub 的 issues 搜索接口,并将返回结果解析到结构体中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package github

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)

const BaseUrl = "https://api.github.com/search/issues"

// 用户信息
type User struct {
Login string `json:"login"`
Id int `json:"id"`
HtmlUrl string `json:"html_url"`
}

// issue 结构
type Issue struct {
Number int
HtmlUrl string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string // Markdown 格式
}

type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issue
}

// 搜索 issue
func SearchIssue(term []string) (*IssuesSearchResult, error) {
q := url.QueryEscape(strings.Join(term, " "))
resp, err := http.Get(BaseUrl + "?q=" + q)
if err != nil {
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("请求失败,状态码:%d", resp.StatusCode)
}

var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}

分类处理数据

这里我写了一个简单的分类逻辑,把 Issues 按时间分成「不到一月」「不到一年」「超过一年」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func Process(data *IssuesSearchResult) {
hash := map[string][]*Issue{
"不到一月": {},
"不到一年": {},
"超过一年": {},
}

for _, val := range data.Items {
now := time.Now()
if now.Year() == val.CreatedAt.Year() && now.Month() == val.CreatedAt.Month() {
hash["不到一月"] = append(hash["不到一月"], val)
} else if now.Year() == val.CreatedAt.Year() {
hash["不到一年"] = append(hash["不到一年"], val)
} else {
hash["超过一年"] = append(hash["超过一年"], val)
}
}

fmt.Printf("数据总条数:%v\n", data.TotalCount)
for key, val := range hash {
fmt.Printf("%s:\n", key)
for _, v := range val {
fmt.Printf("#%-5d %9.9s %.55s %9.9v\n",
v.Number, v.User.Login, v.Title, v.CreatedAt)
}
}
}

main.go 入口

最后写一个简单的 main.go,从命令行传入参数,调用 API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"log"
"os"
github "study_go/pkg"
)

func main() {
terms := os.Args[1:]
if len(terms) == 0 {
log.Fatal("请提供搜索关键词,例如:go run . repo:golang/go json")
}

result, err := github.SearchIssue(terms)
if err != nil {
log.Fatal(err)
}
fmt.Println("搜索关键词:", terms)

github.Process(result)

fmt.Println("程序执行完毕 ✅")
}

运行效果

比如我执行:

1
go run . repo:golang/go json

输出:

1
2
3
4
5
6
7
8
9
搜索关键词:[repo:golang/go json]
数据总条数:256
不到一月:
#12345 octocat encoding/json bug 2025-08-20
不到一年:
#12001 alice Improve json docs 2025-03-10
超过一年:
#8888 bob Old json issue 2023-07-05
程序执行完毕 ✅

总结

  • SearchIssue 封装了请求和 JSON 解析
  • Process 里演示了如何遍历和分类数据
  • main.go 做了一个小命令行工具

这是一个很小的例子,但能让我们快速熟悉:

  1. Go 里怎么用 http 请求 API
  2. 如何用 json.NewDecoder 直接解码到结构体
  3. 如何处理和展示结果