社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
本文为《Go in Action》的第二章读书笔记。
第二章主要是介绍了一个go语言的示例应用。
A: 简答来说,就是将配置文件里面的rss源读取出来,然后把源的内容拉取下来,在各个源的内容里面搜索一个文字,显示结果。
A: 文件结构如下:
sample/ #目录结构
├── data
│ └── data.json #存放的rss源地址,以json的格式
├── main.go #程序入口main文件
├── matchers #匹配程序,rss是一种源类型,后续可以扩展
│ └── rss.go
└── search #主要逻辑代码
├── default.go
├── feed.go
├── match.go
└── search.go
后续会对各个文件进行分析:
首先看看内容:
package main
import (
"log"
"os"
_ "sample/matchers"
"sample/search"
)
func init() {
log.SetOutput(os.Stdout)
}
// main is the entry point for the program
func main() {
search.Run("president")
}
几个点:
这里面包含了rss的地址和名字:
[
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1001",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1008",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1006",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1007",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1057",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1021",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1012",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=1003",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=2",
"type" : "rss"
},
{
"site" : "npr",
"link" : "http://www.npr.org/rss/rss.php?id=3",
"type" : "rss"
}
]
其为一个json数组,每个元素有site、link和type三个字段。
feed表示的就是一个rss的源。看看源码:
package search
import (
"encoding/json"
"log"
"os"
)
const dataFile = "data/data.json"
// Feed contains information we need to process a feed.
type Feed struct {
Name string `json:"site"`
URI string `json:"link"`
Type string `json:"type"`
}
// RetrieveFeeds reads and unmarshals the feed data file
func RetrieveFeeds() ([]*Feed, error) {
file, err := os.Open(dataFile)
if err != nil {
return nil, err
}
defer file.Close()
var feeds []*Feed
err = json.NewDecoder(file).Decode(&feeds)
log.Printf("Retrieve feeds result: %vn", feeds)
return feeds, err
}
如下:
The keyword defer is used to schedule a function call to be executed right after a function returns. It’s our responsibility to close the file once we’re done with it. By using the keyword defer to schedule the call to the close method, we can guarantee that the method will be called.This will happen even if the function panics and terminates unexpectedly.
就算函数非正常终止了,也会执行该defer的操作。
先看源码:
package search
// defaultMatcher implements the default matcher.
type defaultMatcher struct{}
func init() {
var matcher defaultMatcher
Register("default", matcher)
}
// Search implements the behavior for the default matcher.
func (m defaultMatcher) Search(feed *Feed, searchTerm string) ([]*Result, error) {
return nil, nil
}
以下:
先看源码:
package search
import "log"
// Result contains the result of a search
type Result struct {
Field string
Content string
}
// Matcher defiens the behavior required by types that want
// to implement a new search type
type Matcher interface {
Search(feed *Feed, searchTerm string) ([]*Result, error)
}
// Match is launched as a goroutine for each individual feed to run
// searches concurrently
func Match(matcher Matcher, feed *Feed, searchTerm string, results chan<- *Result) {
searchResults, err := matcher.Search(feed, searchTerm)
if err != nil {
log.Println(err)
return
}
for _, result := range searchResults {
results <- result
}
}
// Display writes results to the console window as they
// are received by the individual goroutines
func Display(results chan *Result) {
// The channel blocks until a result is written to the channel.
// Once the channel is closed the for loop terminates.
for result := range results {
log.Printf("%s:n%snn", result.Field, result.Content)
}
}
如下:
:=
符号。该符号表示同时定义并初始化变量先看源码:
package search
import (
"log"
"sync"
)
var matchers = make(map[string]Matcher)
// Run performs
func Run(searchTerm string) {
feeds, err := RetrieveFeeds()
if err != nil {
log.Fatal(err)
}
// Create an unbuffered channel to receive match results to display
results := make(chan *Result)
// Setup a wait group so we can process all the feeds
var waitGroup sync.WaitGroup
// Set the number of go routines we need to wait for while
// they process the individual feeds.
waitGroup.Add(len(feeds))
// Launch a goroutine for each feed to find the results.
for _, feed := range feeds {
// Retrieve a matcher for the search.
matcher, exists := matchers[feed.Type]
if !exists {
matcher = matchers["default"]
}
// Launch the goroutine to perform the search
go func(matcher Matcher, feed *Feed) {
Match(matcher, feed, searchTerm, results)
waitGroup.Done()
}(matcher, feed)
}
// Launch a goroutine to monitor when all the work is done.
go func() {
waitGroup.Wait()
//Close the channel to signal to the Display
// function that we can exit the program
close(results)
}()
Display(results)
}
// Register is called to register a matcher for use by the program.
func Register(feedType string, matcher Matcher) {
if _, exists := matchers[feedType]; exists {
log.Fatalln(feedType, "Matcher already registered")
}
log.Println("Register", feedType, "matcher")
matchers[feedType] = matcher
}
如下:
var matchers = make(map[string]Matcher)
,创建了一个map,其key为string类型,值为Matcher类型。注意Matcher类型在match.go里面进行了定义,为一个interface。这个matchers定义在了函数的外面,是一个package level的变量。在Register函数里面进行了键值对的添加log.Fatal
会在结束程序前打印信息results := make(chan *Result)
,创建Result Channel源码:
package matchers
import (
"encoding/xml"
"errors"
"fmt"
"log"
"net/http"
"regexp"
"sample/search"
)
type (
// item defines the fields associated with the item tag
// in the rss document.
item struct {
XMLName xml.Name `xml:"item"`
PubDate string `xml:"pubDate"`
Title string `xml:"title"`
Description string `xml:"description"`
Link string `xml:"link"`
GUID string `xml:"guid"`
GeoRssPoint string `xml:"georss:point"`
}
// image defines the fields associated with the image tag
// in the rss document.
image struct {
XMLName xml.Name `xml:"image"`
URL string `xml:"url"`
Title string `xml:"title"`
Link string `xml:"link"`
}
// channel defines the fields associated with the channel tag
// in the rss document.
channel struct {
XMLName xml.Name `xml:"channel"`
Title string `xml:"title"`
Description string `xml:"description"`
Link string `xml:"link"`
PubDate string `xml:"pubDate"`
LastBuildDate string `xml:"lastBuildDate"`
TTL string `xml:"ttl"`
Language string `xml:"language"`
ManagingEditor string `xml:"managingEditor"`
WebMaster string `xml:"webMaster"`
Image image `xml:"image"`
Item []item `xml:"item"`
}
// rssDocument defines the fields associated with the rss document.
rssDocument struct {
XMLName xml.Name `xml:"rss"`
Channel channel `xml:"channel"`
}
)
// rssMatcher implements the Matcher interface
type rssMatcher struct{}
// init registers the matcher with the program
func init() {
var matcher rssMatcher
log.Println("register rss matcher")
search.Register("rss", matcher)
}
func (m rssMatcher) Search(feed *search.Feed, searchTerm string) ([]*search.Result, error) {
var results []*search.Result
log.Printf("Search Feed Type[%s] Site[%s] For URI[%s]n", feed.Type, feed.Name, feed.URI)
// Retrieve the data to search.
document, err := m.retrieve(feed)
if err != nil {
return nil, err
}
for _, channelItem := range document.Channel.Item {
// Check the title for the search term.
matched, err := regexp.MatchString(searchTerm, channelItem.Title)
if err != nil {
return nil, err
}
// If we found a match save the result
if matched {
results = append(results, &search.Result{
Field: "Title",
Content: channelItem.Title, // 注意此处的逗号哦,很容易遗忘的
})
}
// Check the description for the search Item
matched, err = regexp.MatchString(searchTerm, channelItem.Description)
if err != nil {
return nil, err
}
if matched {
results = append(results, &search.Result{
Field: "Description",
Content: channelItem.Description,
})
}
}
return results, nil
}
func (m rssMatcher) retrieve(feed *search.Feed) (*rssDocument, error) {
if feed.URI == "" {
return nil, errors.New("No rss feed uri provided")
}
resp, err := http.Get(feed.URI)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP Response Error %dn", resp.StatusCode)
}
var document rssDocument
err = xml.NewDecoder(resp.Body).Decode(&document)
return &document, err
}
如下:
type ()
定义了四个类型:rssDocument、channel、image、item。rssDocument包括了channel,channel包括了image和item数组。有几点:
$GOPATH/src/sample
文件夹里面。放这里面import的时候才能用import sample/...
go run .
package search
如果你也想读书写笔记,或者对QKQ感兴趣,请加我微信,一起来哦,添加的时候请备注读书
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!