diff --git a/pkg/GithubApi/api.go b/pkg/GithubApi/api.go new file mode 100644 index 0000000..a7bf434 --- /dev/null +++ b/pkg/GithubApi/api.go @@ -0,0 +1,107 @@ +package GithubApi + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" +) + +func SearchIssues(terms []string, queryDate int, appendArgs string) (*IssuesSearchResult, error) { + // 指定查询日期 + var resTime = time.Now() + const layout = "2006-01-02" // 确定 日期格式化样本 + resTime = resTime.AddDate(0, 0, -(queryDate)) // 确定查询后日期 + finalDate := " created:>" + resTime.Format(layout) // 拼接参数 + fmt.Println(finalDate) + + // 处理查询主体 + q := strings.Join(terms, " ") + q += finalDate + q = url.QueryEscape(q) + appendArgs + fmt.Println(IssuesURL + "?q=" + q) + resp, err := http.Get(IssuesURL + "?q=" + q) + if err != nil { + return nil, err + } + + // We must close resp.Body on all execution paths. + // (Chapter 5 presents 'defer', which makes this simpler.) + if resp.StatusCode != http.StatusOK { + err := resp.Body.Close() + if err != nil { + return nil, err + } + return nil, fmt.Errorf("search query failed: %s", resp.Status) + } + + var result IssuesSearchResult + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + err := resp.Body.Close() + if err != nil { + return nil, err + } + return nil, err + } + err = resp.Body.Close() + if err != nil { + return nil, err + } + return &result, nil +} + +func CategoryOutput(res *IssuesSearchResult) { + const layout = "2006-01-02 15:04:05" + // 时间 + now := time.Now() + lastDayDate := now.AddDate(0, 0, -1) + last7DayDate := now.AddDate(0, 0, -7) + last30DayDate := now.AddDate(0, 0, -30) + + var inADay []*Issue + var in7Day []*Issue + var in30Day []*Issue + var largeThan30 []*Issue + for _, item := range res.Items { + Create := item.CreatedAt + if Create.Before(last30DayDate) { + // 大于30天 + largeThan30 = append(largeThan30, item) + } else if Create.Before(last7DayDate) && (Create.After(last30DayDate)) { + // 大于7天小于30天 + in30Day = append(in30Day, item) + } else if Create.Before(lastDayDate) && (Create.After(last7DayDate)) { + // 大于1天 小于7天 + in7Day = append(in7Day, item) + } else { + // 1天以内 + inADay = append(inADay, item) + } + } + fmt.Printf("%d issues:\n", res.TotalCount) + if *IsCategory { + fmt.Println("一天以内:") + for _, item := range inADay { + fmt.Println(item.CreatedAt.Format(layout), item.Title) + } + fmt.Println("7天以内:") + for _, item := range in7Day { + fmt.Println(item.CreatedAt.Format(layout), item.Title) + } + fmt.Println("30天以内:") + for _, item := range in30Day { + fmt.Println(item.CreatedAt.Format(layout), item.Title) + } + fmt.Println("30天以外:") + for _, item := range largeThan30 { + fmt.Println(item.CreatedAt.Format(layout), item.Title) + } + } else { + for _, item := range res.Items { + fmt.Printf("#%-5d %9.9s %.55s\n", + item.Number, item.User.Login, item.Title) + } + } +} diff --git a/pkg/GithubApi/struct.go b/pkg/GithubApi/struct.go index dcac0e0..046db1e 100644 --- a/pkg/GithubApi/struct.go +++ b/pkg/GithubApi/struct.go @@ -1 +1,29 @@ package GithubApi + +import ( + "time" +) + +const IssuesURL = "https://api.github.com/search/issues" + +type IssuesSearchResult struct { + TotalCount int `json:"total_count"` + Items []*Issue +} + +type Issue struct { + Number int + HTMLURL string `json:"html_url"` + Title string + State string + *User + CreatedAt time.Time `json:"created_at"` + Body string // in Markdown format +} + +type User struct { + Login string + HTMLURL string `json:"html_url"` +} + +var IsCategory *bool diff --git a/pkg/github/github.go b/pkg/github/github.go new file mode 100644 index 0000000..125f913 --- /dev/null +++ b/pkg/github/github.go @@ -0,0 +1,66 @@ +// Package github provides a Go API for the GitHub issue tracker. +// See https://developer.github.com/v3/search/#search-issues. +package github + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" +) + +const IssuesURL = "https://api.github.com/search/issues" + +type IssuesSearchResult struct { + TotalCount int `json:"total_count"` + Items []*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 // in Markdown format +} + +type User struct { + Login string + HTMLURL string `json:"html_url"` +} + +// 和前面一样,即使对应的JSON对象名是小写字母, +// 每个结构体的成员名也是声明为大写字母开头的。 +// 因为有些JSON成员名字和Go结构体成员名字并不相同, +// 因此需要Go语言结构体成员Tag来指定对应的JSON名字。 +// 同样,在解码的时候也需要做同样的处理, +// GitHub服务返回的信息比我们定义的要多很多。 + +// SearchIssues queries the GitHub issue tracker. + +func SearchIssues(terms []string) (*IssuesSearchResult, error) { + q := url.QueryEscape(strings.Join(terms, " ")) + resp, err := http.Get(IssuesURL + "?q=" + q) + if err != nil { + return nil, err + } + + // We must close resp.Body on all execution paths. + // (Chapter 5 presents 'defer', which makes this simpler.) + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return nil, fmt.Errorf("search query failed: %s", resp.Status) + } + + var result IssuesSearchResult + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + resp.Body.Close() + return nil, err + } + resp.Body.Close() + return &result, nil +} diff --git a/src/study/day6/Practice4.10.go b/src/study/day6/Practice4.10.go new file mode 100644 index 0000000..dbe483a --- /dev/null +++ b/src/study/day6/Practice4.10.go @@ -0,0 +1,41 @@ +package main + +import ( + "Study/pkg/GithubApi" + "flag" + "fmt" + "log" + "strconv" +) + +func main() { + var finalArgs string + + flagDate := flag.Int("d", 30, "指定查询日期") + flagPerPage := flag.Int("n", 30, "指定每页返回多少条Issues") + flagPage := flag.Int("p", 1, "返回第几页的结果") + GithubApi.IsCategory = flag.Bool("c", false, "是否按日,月,年分类") + + // 从os.Args[1:]中解析注册的flag。必须在所有flag都注册好而未访问其值时执行。 + // 未注册却使用flag -help时,会返回ErrHelp。 + if !flag.Parsed() { + flag.Parse() + } + + // 指定每页返回多少条结果 + finalArgs = "&per_page=" + strconv.Itoa(*flagPerPage) + // 指定返回页 + finalArgs += "&page=" + strconv.Itoa(*flagPage) + + result, err := GithubApi.SearchIssues(flag.Args(), *flagDate, finalArgs) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%T", result) + fmt.Printf("\nissues:%d\n", result.TotalCount) + //for _, item := range result.Items { + // fmt.Printf("#%-5d %9.9s %.55s\n", + // item.Number, item.User.Login, item.Title) + //} + GithubApi.CategoryOutput(result) +} diff --git a/src/study/day6/json.go b/src/study/day6/json.go new file mode 100644 index 0000000..770d42e --- /dev/null +++ b/src/study/day6/json.go @@ -0,0 +1,60 @@ +package main + +import ( + "Study/pkg/github" + "encoding/json" + "fmt" + "log" + "os" +) + +type Movie struct { + Title string + Year int `json:"released"` + Color bool `json:"color,omitempty"` + Actors []string +} + +var movies = []Movie{ + {Title: "Casablanca", Year: 1942, Color: false, + Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}}, + {Title: "Cool Hand Luke", Year: 1967, Color: true, + Actors: []string{"Paul Newman"}}, + {Title: "Bullitt", Year: 1968, Color: true, + Actors: []string{"Steve McQueen", "Jacqueline Bisset"}}, + // ... +} + +// 这样的数据结构特别适合JSON格式,并且在两者之间相互转换也很容易。 +// 将一个Go语言中类似movies的结构体slice转为JSON的过程叫编组(marshaling)。 +// 编组通过调用json.Marshal函数完成: +func main() { + //data, err := json.Marshal(movies) + data, err := json.MarshalIndent(movies, "", "\t") + if err != nil { + log.Fatalf("JSON marshaling failed: %s", err) + } + fmt.Printf("%s\n", data) + + // 编码的逆操作是解码,对应将JSON数据解码为Go语言的数据结构, + // Go语言中一般叫unmarshaling,通过json.Unmarshal函数完成。 + // 下面的代码将JSON格式的电影数据解码为一个结构体slice,结构体中只有Title成员。 + // 通过定义合适的Go语言数据结构,我们可以选择性地解码JSON中感兴趣的成员。 + // 当Unmarshal函数调用返回,slice将被只含有Title信息的值填充,其它JSON成员将被忽略。 + var titles []struct{ Title string } + if err := json.Unmarshal(data, &titles); err != nil { + log.Fatalf("JSON unmarshaling failed: %s", err) + } + fmt.Println(titles) + + result, err := github.SearchIssues(os.Args[1:]) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%d issues:\n", result.TotalCount) + for _, item := range result.Items { + fmt.Printf("#%-5d %9.9s %.55s\n", + item.Number, item.User.Login, item.Title) + } + +}