《Go语言圣经/gopl》第四章习题下

前言

本章为《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)
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值