文章目录
前言
本章为《Go语言圣经》第四章后半部分即4.10-4.14,主要为编程应用题。前半部分4.1-4.9为算法题与语法题,文字阐述量较大,故笔者将其分为上下两章进行分开叙述。
项目源码地址
4.10-4.11
前置知识
题目代码
./github/const.go
// Package github provides a Go API for the GitHub issue tracker.
// See https://developer.github.com/v3/search/#search-issues.
package github
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 *User
CreatedAt time.Time `json:"created_at"`
Body string // in Markdown format
}
type User struct {
Login string
HTMLURL string `json:"html_url"`
}
./github/function.go
package github
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
// 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
}
./issues.go
// Issues prints a table of GitHub issues matching the search terms.
package main
import (
"fmt"
"log"
"os"
"gopl.io/ch4/github"
)
func main() {
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)
}
}
4.10
问题
修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。
解析
可以注意到,题目所给的const常量中Issue结构体中包含了CreatedAt time.Time常量可以使用**time.Since()转换成距离当前时间的毫秒数再使用time.Hour()**转换成小时数即可知道问题到当前的时间是多少,再将其分类放入三个Slice中即可
代码
仅对issues函数进行修改即可
// Issues prints a table of GitHub issues matching the search terms.
package main
import (
"fmt"
"log"
"os"
"time"
"github.com/ChenMiaoQiu/learning-go-example/ch4/4.10/github"
)
func main() {
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d issues:\n", result.TotalCount)
var month, year, overYear []github.Issue
for _, item := range result.Items {
len := time.Since(item.CreatedAt).Hours()
day := len / 24
if day <= 30 {
month = append(month, *item)
} else if day <= 365 {
year = append(year, *item)
} else {
overYear = append(overYear, *item)
}
}
fmt.Println("Less than a month")
for _, item := range month {
fmt.Printf("#%-5d %9.9s %.55s\n",
item.Number, item.User.Login, item.Title)
}
fmt.Println("Less than a year")
for _, item := range year {
fmt.Printf("#%-5d %9.9s %.55s\n",
item.Number, item.User.Login, item.Title)
}
fmt.Println("Over a year")
for _, item := range overYear {
fmt.Printf("#%-5d %9.9s %.55s\n",
item.Number, item.User.Login, item.Title)
}
}
4.11
问题
编写一个工具,允许用户在命令行创建、读取、更新和关闭GitHub上的issue,当必要的时候自动打开用户默认的编辑器用于输入文本信息。
解析
如何调用github的api?
创建 issue:使用 POST /repos/{owner}/{repo}/issues 端点来创建一个新的 issue。在请求中,你需要提供仓库的所有者用户名和仓库名称,以及 issue 的标题和内容。
获取 issue 列表:使用 GET /repos/{owner}/{repo}/issues 端点来获取仓库中的 issue 列表。你可以提供不同的查询参数来过滤和排序结果。
获取单个 issue:使用 GET /repos/{owner}/{repo}/issues/{issue_number} 端点来获取特定 issue 的详细信息。你需要提供仓库的所有者用户名、仓库名称和 issue 编号。
更新 issue:使用 PATCH /repos/{owner}/{repo}/issues/{issue_number} 端点来更新现有的 issue。在请求中,你可以提供要更新的字段和值。
关闭 issue:使用 PATCH /repos/{owner}/{repo}/issues/{issue_number} 端点来关闭一个 issue。在请求中,你需要将 issue 的状态设置为 “closed”。
代码
使用对应方法调用对应api即可,此处仅给出api调用方法
package github
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
// GET /repos/{owner}/{repo}/issues
func (p Params) GetIssues() ([]Issue, error) {
url := IssuesURL + p.Owner + "/" + p.Repo + "/issues"
fmt.Println(url)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var issues []Issue
if err := json.NewDecoder(resp.Body).Decode(&issues); err != nil {
return nil, err
}
return issues, nil
}
// GET /repos/{owner}/{repo}/issues/{issue_number}
func (p Params) GetIssue() (Issue, error) {
url := IssuesURL + p.Owner + "/" + p.Repo + "/issues/" + p.Number
resp, err := http.Get(url)
if err != nil {
return Issue{}, err
}
defer resp.Body.Close()
var issue Issue
if err := json.NewDecoder(resp.Body).Decode(&issue); err != nil {
return Issue{}, err
}
return issue, nil
}
// POST /repos/{owner}/{repo}/issues
func (p Params) CreateIssue() bool {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(p.Issue); err != nil {
return false
}
url := IssuesURL + p.Owner + "/" + p.Repo + "/issues" +
"?access_token=" + p.Token
_, err := http.Post(url, "application/json", &buf)
return err == nil
}
// PATCH /repos/{owner}/{repo}/issues/{issue_number}
func (p Params) EditIssue() bool {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(p.Issue); err != nil {
return false
}
url := IssuesURL + p.Owner + "/" + p.Repo + "/issues/" + p.Number + "?access_token=" + p.Token
request, err := http.NewRequest("PATCH", url, &buf)
if err != nil {
return false
}
request.Header.Set("Content-Type", "application/json")
_, err = http.DefaultClient.Do(request)
return err == nil
}
// PATCH /repos/{owner}/{repo}/issues/{issue_number}
func (p Params) CloseIssue() bool {
url := IssuesURL + p.Owner + "/" + p.Repo + "/issues/" + p.Number + "?access_token=" + p.Token
request, err := http.NewRequest("PATCH", url, nil)
if err != nil {
return false
}
request.Header.Set("Content-Type", "application/json")
_, err = http.DefaultClient.Do(request)
return err == nil
}
4.12
4.12
问题
流行的web漫画服务xkcd也提供了JSON接口。例如,一个 https://xkcd.com/571/info.0.json 请求将返回一个很多人喜爱的571编号的详细描述。下载每个链接(只下载一次)然后创建一个离线索引。编写一个xkcd工具,使用这些离线索引,打印和命令行输入的检索词相匹配的漫画的URL。
解析
先通过GET方法获取https://xkcd.com/571/info.0.json信息,将其存入本地建立离线索引,再通过打开本地文件进行查询输出URL即可,注意连接地址中并未给出题目中的URL,故笔者输出对应漫画封面图片连接
代码
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type Xkcd struct {
Title string
Url string
Img string
}
const baseUrl = "https://xkcd.com/"
func getOfflineIndex() {
out, err := os.Create("offlineIndex.txt")
if err != nil {
fmt.Println("error !!!")
return
}
defer out.Close()
write := csv.NewWriter(out)
write.Write([]string{"title", "link", "img"})
for i := 1; i <= 120; i++ {
xkcd := &Xkcd{}
url := fmt.Sprintf("%s%d/info.0.json", baseUrl, i)
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return
}
if err := json.NewDecoder(resp.Body).Decode(xkcd); err != nil {
resp.Body.Close()
fmt.Println(err)
}
resp.Body.Close()
write.Write([]string{xkcd.Title, xkcd.Url, xkcd.Img})
write.Flush()
}
}
func resLink(target string) {
f, err := os.Open("./offlineIndex.txt")
if err != nil {
fmt.Println("can't find offline index")
return
}
defer f.Close()
reader := csv.NewReader(f)
for {
res, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
fmt.Println(err)
}
if target == res[0] {
fmt.Println(res[2])
return
}
}
fmt.Println("can't find !!!")
}
func main() {
f, err := os.Open("./offlineIndex.txt")
if err != nil {
getOfflineIndex()
}
f.Close()
for _, val := range os.Args[1:] {
resLink(val)
}
}
4.13
4.13
问题
使用开放电影数据库的JSON服务接口,允许你检索和下载 https://omdbapi.com/ 上电影的名字和对应的海报图像。编写一个poster工具,通过命令行输入的电影名字,下载对应的海报。
解析
如何申请一个posterAPI?
进入网站后点击API Key
选择FREE,依次填写对应信息即可,填写完成后1-2分钟内邮箱就会收到对应token,直接复制使用即可
使用GET方法调用后得到结果如下
Poster即为海报连接,编写下载函数,下载到本地即可
代码
// go run .\main.go Minions
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const baseUrl = "http://www.omdbapi.com/?i=tt3896198&apikey=ffa28a90"
func downLoadImg(imgUrl string) {
resp, err := http.Get(imgUrl)
if err != nil {
fmt.Println("download error !!!")
return
}
defer resp.Body.Close()
flie, err := os.Create("output.jpg")
if err != nil {
fmt.Println("download error !!!")
return
}
defer flie.Close()
_, err = io.Copy(flie, resp.Body)
if err != nil {
return
}
}
func getPoster(name string) {
url := baseUrl + "&t=" + name
var posterUrl struct {
Poster string
}
resp, err := http.Get(url)
if err != nil {
fmt.Println("can't find")
return
}
if resp.StatusCode != http.StatusOK {
fmt.Println("cant't find")
return
}
if err := json.NewDecoder(resp.Body).Decode(&posterUrl); err != nil {
fmt.Println(err)
return
}
imageUrl := posterUrl.Poster
downLoadImg(imageUrl)
}
func main() {
for _, name := range os.Args[1:] {
getPoster(name)
}
}
4.14
4.14
问题
创建一个web服务器,查询一次GitHub,然后生成BUG报告、里程碑和对应的用户信息。
解析
使用4.10中的issue函数和1.7节的web服务章节组合调用即可。
注意 可以将html代码存至本地文件中使用**template.Must(template.ParseFiles())**方法调用
代码
此处仅给出主要代码
package main
import (
"html/template"
"log"
"net/http"
"github.com/ChenMiaoQiu/learning-go-example/ch4/4.14/issue"
)
func main() {
http.HandleFunc("/", Handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func Handler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
log.Println(err)
}
q := r.FormValue("key")
result, err := issue.SearchIssues(q)
if err != nil {
log.Println(err)
}
tmpl := template.Must(template.ParseFiles("index.html"))
if err := tmpl.Execute(w, result); err != nil {
log.Println(err)
}
}