不安全Golang重构笔记 – PaperCache

  • A+
所属分类:技术关注

不安全Golang重构笔记 - PaperCache

关于不安全

https://buaq.net https://f5.pm
不安全是一个我一直以来获取我感兴趣相关的rss阅读器,与公开的阅读器不同的是我会对文章正文和图片做永久的存储,很多时候看到的好的文章过一段时间可能站就关了,而且纯靠书签方式进行管理并不是特别的方便,所以诞生不安全。
这次重构主要是因为上一个版本是差不多我6年前用php写的,包括后端的爬虫,缓存等,写的时候没有考虑太多,加上中间硬塞了一个新功能导致代码面目全非,这次就用go进行重构,顺便也给文章正文用前段时间刚出的zinc做了索引服务,对用户来说最大的变化就是支持全文检索了。

支持功能

  • [x] 全文索引
  • [x] telegram机器人推送
  • [x] twitter推送
  • [x] 文章定时爬取
  • [x] 支持http/socks5代理
  • [x] 支持cloudflare woker代理
  • [x] 收藏功能
  • [x] api添加文章功能
  • [x] 更好的正文提取
  • [x] 图片自动上传到百度云对象存储
  • [ ] 工具分享
  • [ ] 文章每日排行榜(兼容微信公众号)

zinc全文索引

不安全Golang重构笔记 - PaperCache

全文索引采用的zinc,其使用的bluge作为底层的索引引擎,再其基础上封装了bulk类似es的查询语法。

有几个坑,第一不支持调整size大小,默认是返回1000条

❯ grep -n -r 1000 *

auth/GetUsers.go:19: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

auth/AuthMiddleware.go:42: searchRequest := bluge.NewTopNSearch(1000, termQuery)

startup/Loadconfig.go:19: return 10000

startup/Loadconfig.go:25: return 10000

uquery/AllDocuments.go:11: searchRequest := bluge.NewTopNSearch(1000, query)

uquery/MatchAllQuery.go:21: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/MatchQuery.go:28: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/MultiPhraseQuery.go:21: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/DateRangeQuery.go:12: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/QueryStringQuery.go:23: searchRequest := bluge.NewTopNSearch(1000, finalQuery).WithStandardAggregations()

uquery/MatchPhraseQuery.go:22: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/WildcardQuery.go:22: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/FuzzyQuery.go:21: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/TermQuery.go:21: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

uquery/PrefixQuery.go:21: searchRequest := bluge.NewTopNSearch(1000, query).WithStandardAggregations()

第二个坑,不支持offset进行调整查询位置,这俩个问题算一个问题,官方暂时也没有支持翻页的计划,这里自己做了一个改动
pkg/core/index.go

func (index *Index) Search(iQuery v1.ZincQuery) (v1.SearchResponse, error) {

var Hits []v1.Hit

var searchRequest bluge.SearchRequest

var err error

....

writer := index.Writer

reader, err := writer.Reader()

if err != nil {

log.Print("error accessing reader: %v", err)

}

dmi, err := reader.Search(context.Background(), searchRequest)

if err != nil {

log.Print("error executing search: %v", err)

}

// highlighter := highlight.NewANSIHighlighter()

var count = 0

// iterationStartTime := time.Now()

next, err := dmi.Next()

for err == nil && next != nil {

// 这边加一个offset

count++

// iQuery结构体我增加了一个Offset字段,可以从前台传过来,这样就可以使用{offset:10,size:10},实现limit 10,10的功能了

if count < iQuery.Offset {

// 他读数据是通过next进行读下一条,内核不了解没啥好办法,只能先通过这样的方式做offset了

next, err = dmi.Next()

continue

}

var result map[string]interface{}

var id string

var timestamp time.Time

err = next.VisitStoredFields(func(field string, value []byte) bool {

if field == "_source" {

json.Unmarshal(value, &result)

return true

} else if field == "_id" {

id = string(value)

return true

} else if field == "@timestamp" {

timestamp, _ = bluge.DecodeDateTime(value)

return true

}

return true

})

if err != nil {

log.Print("error accessing stored fields: %v", err)

}

hit := v1.Hit{

Index: index.Name,

Type: index.Name,

ID: id,

Score: next.Score,

Timestamp: timestamp,

Source: result,

}

next, err = dmi.Next()

// results = append(results, result)

Hits = append(Hits, hit)

// 默认他iQuery结构提是存在size字段的,但是没有通过他进行调整大小,我们自己加一个判断,这里理论是来说使用len会影响性能

if len(Hits) > iQuery.Size {

break

}

}

if err != nil {

log.Print("error iterating results: %v", err)

}

....

reader.Close()

return resp, nil

}

第三个坑是没有类似logstash那样可以直接导入数据到es的第三方工具,这里我直接给他加了个功能从mysql里面读数据进去

func ReadFromDB() {

var notes []Notes

// 从数据库里读取出所有的数据

DB.Model(&Notes{}).Find(&notes)

for _, note := range notes {

log.Println("[*] Import ", note.Hash)

// 从文件读取文章正文并提取纯文本部分的数据

note.Content = GetNoteContent(note)

if ret, err := json.Marshal(&note); err == nil {

var doc map[string]interface{}

// 最后把struct转换成map

json.Unmarshal(ret, &doc)

ImportData("buaqbatchImport", doc)

}

}

}

func ImportData(indexName string, doc map[string]interface{}) {

if !core.IndexExists(indexName) {

//这部分的代码基本是直接抄的他前台createDocument部分的代码

newIndex, err := core.NewIndex(indexName)

if err != nil {

log.Print(err)

return

}

core.ZINC_INDEX_LIST[indexName] = newIndex // Load the index in memory

}

index := core.ZINC_INDEX_LIST[indexName]

docID := uuid.New().String()

// 他创建document需要传一个uuid和一个map的结构体

err := index.UpdateDocument(docID, &doc)

log.Default().Println(err)

}

分词

说搜索就不得不说分词,一开始尝试了go版本的jieba分词
后来因为其还是用c然后用cgo进行调用,编译之后会依赖glibc,在某些机器上因为glibc版本过低不能运行,哪怕用xgo编译也不行,后来找到了sego整体用下来也还可以,sego是纯go开发的,不用担心glibc版本的问题。

Rss抓取

这里的rss爬取用的是gofeed,官方号称支持如下版本,暂时没有遇到坑

  • RSS 0.90
  • Netscape RSS 0.91
  • Userland RSS 0.91
  • RSS 0.92
  • RSS 0.93
  • RSS 0.94
  • RSS 1.0
  • RSS 2.0
  • Atom 0.3
  • Atom 1.0
  • JSON 1.0
  • JSON 1.1

正文提取

试了如下基本版本的正文提取

如果硬要说坑的话,可能就会有潜在的xss风险,他对正文提取似乎还是比较包容,没有删除太多的标签,这点后面有空优化一下。

其他

发现了一个老版本的问题,就是图片有时候没办法缓存,主要原因是 ip被目标网站ban掉了,这边直接通过cf的worker做proxy去抓取内容。
zinc这边对在建立索引的时候对cpu要求还是高,后期可以考虑加一台机器专门做搜索。

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin