博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
自制信息检索网站(三)——Go语言标准库实现后端
阅读量:7047 次
发布时间:2019-06-28

本文共 4836 字,大约阅读时间需要 16 分钟。

前两篇博客介绍到了掘金数据的爬取和分析,而我们的最终目的是搭建一个小型的检索系统。当然数据全都是偷掘金的了(笑)。目前这个项目已经搭建完毕了,已经发布到github上面了,另外我也之前做了一个伯乐在线的小型检索系统,地址在这。

下面就进入今天的正题,关于如何使用Go语言搭建一个简单的后台项目。由于在信息搜索上面使用到了ElasticSearch这个非常棒的开源项目,所以也使用了Go语言版的elastic实现了内容搜索,这也是这个后台项目唯一的第三方依赖,其它都为Go语言标准库实现的。项目结构入如下图所示:

在static文件夹下是前端的资源部分,view文件夹下是前端视图部分,juejin文件夹下是后台项目的依赖,main.go文件是整个项目的入口,接下来让我们由浅入深的看一下整个项目。

utils

因为这个项目的场景不大单纯的只是涉及了内容检索以及资源服务,所以utils这里面的东西并没有太多,而由于Go语言的特性,它会经常涉及error的检查,所以整个utils.go里面也就只有关于error的处理的两个方法。

// print the errorfunc ErrorPrint(err error) bool {	if err != nil {		log.Println(err)		return false	}	return true}复制代码

第一个方法,由于在使用elastic查询内容时不可避免的会出现error,可是又不能让程序crash掉,于是便有了这个ErrorPrint方法。

// fatal the error then the program will be brokenfunc ErrorFatal(err error) {	if err != nil {		log.Fatal(err)		return	}}复制代码

第二个方法,是用于程序出现了严重错误,没有必要在继续运行下去所定义的方法,对于严重的error如在http在ListenAndServe时出错,便可以直接Fatal。

log

顾名思义,这个log.go里面实现了日志打印的功能,对于检测程序运行情况,以及查看请求的信息,一个简单的日志打印功能是十分有必要的。

func WriteLog(r *http.Request, t time.Time, match string, pattern string) {	d := time.Now().Sub(t)	l := fmt.Sprintf("[ACCESS] | % -10s | % -40s | % -16s | % -10s | % -40s |", Bold(Blue(r.Method)), r.URL.Path, d.String(), Red(match), Green(pattern))	log.Println(l)}复制代码

这个函数会对请求的路径和方法,以及响应时间,匹配路径进行日志输出。至于里面的Red(),Green(),Bold()均是为了改变在终端里面的字体颜色,让整个日志信息更为清晰。效果图如下:

elastic

前面也说到,整个项目会不断的涉及文章检索,内容推荐的操作,而这些便都需要使用elasticsearch来完成,因此笔者把所有关于与elasticsearch交互的功能写在了这个文件里面,其实它也很简单,只是接收请求的参数并将其结构化用来请求elasticsearch。首先看这个getItems函数。

// get the article items from the elastic serverfunc getItems(keyword string, page int) *elastic.SearchHits {	// search result from the tags,title,content of the article item	query := elastic.NewMultiMatchQuery(keyword, "tags", "title", "content")	result, err := client.Search().			Index("juejin").			Query(query).			From((page - 1) * 10).Size(10).			Do(ctx)	if ok := ErrorPrint(err); ok {		return result.Hits	}	return nil}复制代码

这个函数接收两个参数keyword和page,即关键词和页数。然后将它们写入elasticsearch的复合查询中,并将hits结果返回。实际的结果如下图:

另外还有一个getSuggestions函数主要用来做搜索框的提示功能,具体实现大同小异,这里边不同阐述。

router

在router.go里面首先定义了一个结构体:

type Controller struct {	HandleFunction func(http.ResponseWriter, *http.Request)	Method         string	Pattern        string}复制代码

这个结构体里面有三个成员,HandleFunction用来处理请求的响应函数,Method用来装填请求方法,Pattern用来匹配请求的路径,这里使用了正则匹配。

func GetItems(w http.ResponseWriter, r *http.Request) {	r.ParseForm()	keyword := r.Form.Get("keyword")	page_str := r.Form.Get("page")	if page_str == "" {		// w.WriteHeader(403)		page_str = "1"	}	page, err := strconv.Atoi(page_str)	if ok := ErrorPrint(err); ok {		w.Header().Set("Content-Type", "application/json")		items := getItems(keyword, page)		if items != nil {			data, err := json.Marshal(items)			ok = ErrorPrint(err)			if ok {				w.Write(data)			}		}	}	w.Write([]byte(""))}复制代码

上面的代码是router.go里面的一个方法,主要用来响应网页对于文章内容的请求,效果图便是上面的效果图了。这个函数首先会调用ParseForm方法解析请求的参数,由于elastic的在page上不能使用空值,因此如果接受到的page参数为空的话会将其处理成1,然后在Header里将返回的数据类型设置为json,并且调用elastic.go里面的getItems函数,通过json的编组函数Marshal得到json格式的数据并返回。router.go里面的其它方法也是如此实现的。

main

由于本次使用的是Go语言http.Server这个结构体实现的服务器,因此就必须自定义一个自己的HttpHandler并实现ServeHTTP这个方法。

type httpHandler struct{}func (*httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {	t := time.Now()	for _, controller := range mux {		if ok, _ := regexp.MatchString(controller.Pattern, r.URL.Path); ok {			if r.Method == controller.Method {				controller.HandleFunction(w, r)				go juejin.WriteLog(r, t, "matched", controller.Pattern)				return			}		}	}	go juejin.WriteLog(r, t, "unmatch", r.URL.Path)	return}复制代码

这个结构体会传到Server.Handler这个参数上,它来实现整个的路由调配。首先它会得到当前的时间t,然后遍历mux这个切片。

func init() {	mux = append(mux, juejin.Controller{juejin.GetItems, "GET", "^/api/getItems"})	mux = append(mux, juejin.Controller{juejin.GetSuggestions, "GET", "^/api/getSuggestions"})	mux = append(mux, juejin.Controller{Static, "GET", "^/static/"})	mux = append(mux, juejin.Controller{DefaultPage, "GET", "^/"})}复制代码

mux这个切片里面存放着所有处理请求的方法。当用户请求的方法通过正则匹配到了我们的路径时便调用这个controller里面的方法即controller.HandleFunction(w, r),然后打印一条日志,如果不匹配,则打印不匹配的日志。

另外mux里面前两个都是数据请求的处理,Static这个方法用于处理静态文件,具体如下:

var wd, _ = os.Getwd()func Static(w http.ResponseWriter, r *http.Request) {	path := filepath.Join(wd, r.URL.Path)	http.ServeFile(w, r, path)}复制代码

我们通过os.Getwd获得当前工作路径,然后通过请求的资源路径并且将这个静态资源通过http.ServeFile方法返回给用户这个资源。

DefaultPage这个方法用于服务于默认的请求页面。

func DefaultPage(w http.ResponseWriter, r *http.Request) {	tmpl, err := template.ParseFiles(filepath.Join(wd, "view/index.html"))	if err != nil {		w.WriteHeader(403)		return	}	err = tmpl.Execute(w, nil)	if err != nil {		w.WriteHeader(403)		return	}}复制代码

这个方法通过html/template这个默认引擎来处理我们的index.html页面,当用户请求时便处理这个模板,然后写入到ResponseWriter中。另外由于我们使用的是正则匹配,并且通过for循环来遍历匹配请求路径,因此必须将"^/"这个路径放在切片的最末尾,否则其它所有请求都会被它匹配,而一旦被它匹配到便会调用DefaltPage这个Controller,其它的Controller便不会调用,整个项目也无法正常运行。好了,由于最近一直在学习Go语言,所以做了项目练手,但确实对路由架构这方面不熟悉,所以整个路由结构非常凌乱。如果您有什么建议,请不吝赐教。

转载地址:http://hbkol.baihongyu.com/

你可能感兴趣的文章
java-Spring 管理bean例子
查看>>
解决关于ios访问相机闪退问题
查看>>
利用ST MCU内部的基准参考电压监测电源电压及其它
查看>>
MySQL 按指定字段自定义列表排序
查看>>
MySQL字段数据全部查出【只保留中文、英文、数字、空格的词表】
查看>>
svn 创建分支、切换分支 及 合并分支 操作
查看>>
[GIt] 团队工作效率分析工具gitstats
查看>>
写给新人的面向对象的基本思维
查看>>
关于分部视图(Partial View)
查看>>
DNS污染——domain name的解析被劫持了返回无效的ip
查看>>
一步一步写一个简单通用的makefile(二)
查看>>
sunspot使用
查看>>
Zombie.js Insanely fast, headless full-stack testing using Node.js
查看>>
POJ2406-Power Strings(kmp循环节)
查看>>
BCM路由全智能固件升级软件tftp,一键刷路由及常用固件下载
查看>>
个人认识:直接断电和发送复位信号给主板有啥区别?
查看>>
测试体会:WAYOS新架构(即二代QOS)的新功能解释
查看>>
UVA 10169 Urn-ball Probabilities !
查看>>
每日一例,练就编程高手
查看>>
no argument specified with option "/LIBPATH:"错误的解决【转载】
查看>>