项目实战:一起看飞机 - 后台

前面已经了解了『一起看飞机』的基本需求,这次我们来搭建一个完整的基于 beego 框架的后台。同时也会涉及调试测试部署等一系列配套工作,我觉得这些反而是工作中很重要的能力,但不知道为啥大部分书都略过了。


系列文章

Go 作为一门非常工程向的语言,虽然比较年轻,但是已经让我很开心了,我想,这应该是我最近会持续投入的一门语言。用这个系列跟大家分享我的学习之路。

番外

开始之前

按照 beego 的安装bee 工具简介 中的介绍把 beegobee 都安装好,然后在 $GOPATH 中我们能看到 bin, pkg, src 三个文件夹,进入 src 文件夹,之后的项目代码都会在这里。接着用以下命令来创建项目

# dawang @ dawang in ~/Documents/GO/src [16-08-24 14:36:44] C:2
$ bee new watch-plane-together
[INFO] Creating application...
/Users/dawang/Documents/GO/src/watch-plane-together/
/Users/dawang/Documents/GO/src/watch-plane-together/conf/
/Users/dawang/Documents/GO/src/watch-plane-together/controllers/
/Users/dawang/Documents/GO/src/watch-plane-together/models/
/Users/dawang/Documents/GO/src/watch-plane-together/routers/
/Users/dawang/Documents/GO/src/watch-plane-together/tests/
/Users/dawang/Documents/GO/src/watch-plane-together/static/
/Users/dawang/Documents/GO/src/watch-plane-together/static/js/
/Users/dawang/Documents/GO/src/watch-plane-together/static/css/
/Users/dawang/Documents/GO/src/watch-plane-together/static/img/
/Users/dawang/Documents/GO/src/watch-plane-together/views/
/Users/dawang/Documents/GO/src/watch-plane-together/conf/app.conf
/Users/dawang/Documents/GO/src/watch-plane-together/controllers/default.go
/Users/dawang/Documents/GO/src/watch-plane-together/views/index.tpl
/Users/dawang/Documents/GO/src/watch-plane-together/routers/router.go
/Users/dawang/Documents/GO/src/watch-plane-together/tests/default_test.go
/Users/dawang/Documents/GO/src/watch-plane-together/main.go
2016/08/24 14:36:51 [SUCC] New application successfully created!

进入 watch-plane-together 文件夹,然后先跑起来试试看:

# dawang @ dawang in ~/Documents/GO/src/watch-plane-together on git:master x [16-08-24 15:56:09] C:130
$ bee run
bee :1.4.1
beego :1.6.1
Go :go version go1.7 darwin/amd64
2016/08/24 15:56:11 [INFO] Uses 'watch-plane-together' as 'appname'
2016/08/24 15:56:11 [INFO] Initializing watcher...
2016/08/24 15:56:11 [TRAC] Directory(/Users/dawang/Documents/GO/src/watch-plane-together/controllers)
2016/08/24 15:56:11 [TRAC] Directory(/Users/dawang/Documents/GO/src/watch-plane-together)
2016/08/24 15:56:11 [TRAC] Directory(/Users/dawang/Documents/GO/src/watch-plane-together/routers)
2016/08/24 15:56:11 [TRAC] Directory(/Users/dawang/Documents/GO/src/watch-plane-together/tests)
2016/08/24 15:56:11 [INFO] Start building...
2016/08/24 15:56:13 [SUCC] Build was successful
2016/08/24 15:56:13 [INFO] Restarting watch-plane-together ...
2016/08/24 15:56:13 [INFO] ./watch-plane-together is running...
2016/08/24 15:56:13 [asm_amd64.s:2086][I] http server Running on :8080

一切正常的话,访问 localhost:8080 就可以见到:

到底发生了什么?结合具体的目录结构,我们来看看:

# dawang @ dawang in ~/Documents/GO/src/watch-plane-together on git:master x [16-08-24 16:02:16]
$ tree ./
./
├── README.md
├── conf
│   └── app.conf
├── controllers
│   └── default.go
├── main.go
├── models
├── routers
│   └── router.go
├── static
│   ├── css
│   ├── favicon.ico
│   ├── img
│   └── js
├── tests
│   └── default_test.go
├── views
│   └── index.tpl
└── watch-plane-together
10 directories, 9 files

我们运行 bee run 之后,程序从 main.go 中开始执行,具体做的工作是把 routers 文件夹中的对应路由规则与具体的控制器进行绑定,比方说 routers/router.go 中有一句为

beego.Router("/", &controllers.MainController{})

然后对应于 controllers/default.go 中的

type MainController struct {
beego.Controller
}
func (c *MainController) Get() {
c.Data["Website"] = "wdxtub.com"
c.Data["Email"] = "dacrocodilee@gmail.com"
c.TplName = "index.tpl"
}

这里我们给 MainController 添加了 Get() 方法,用于处理基本的 get 请求。而在具体的模板中,使用两个大括号加点号(这里直接输入会导致 hexo 解析异常,所以就用文字描述了)进行引用。

最后,我们可以在 conf/app.conf 中添加一行 EnableAdmin = true,我们就可以在 localhost:8088 中见到一个监控页面,像这样:

基本的套路就是这么简单!然后我们来简单设计下 API,方便之后的开发。

API 规划

服务 API 的设计其实不算特别难,类似于起函数名。不过随着项目的不断开发,随意的命名会导致项目复杂度指数爆炸。所以我们要采用更为现代化的套路 - RESTful API。

RESTful

在我看来 RESTful API 和成语差不多,通过一定的规约和沉淀,把互联网连接的低语境拔高的高语境,所谓高语境的意思是大家已经了解一组通用的条件和原则,在此基础上沟通可以极大降低沟通成本。

先来看几个基本概念:

  • 资源(Resources):我们平常上网访问的一张图片、一个文档、一个视频等。这些资源我们通过URI来定位,也就是一个URI表示一个资源
  • 表现(Representation):资源是做一个具体的实体信息,他可以有多种的展现方式。而把实体展现出来就是表现层,例如一个 txt 文本信息,他可以输出成 html、json、xml 等格式,一个图片他可以 jpg、png 等方式展现,这个就是表现层的意思。 URI 确定一个资源,而在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定,这两个字段才是对”表现层”的描述
  • 状态转化(State Transfer):访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,肯定涉及到数据和状态的变化。而 HTTP 协议是无状态的,那么这些状态肯定保存在服务器端,所以如果客户端想要通知服务器端改变数据和状态的变化,肯定要通过某种方式来通知它。客户端能通知服务器端的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。

简单点来说,RESTful 的核心思想类似于面向对象,把原先的过程导向转变为资源导向,然后围绕着资源做文章。

这里需要保证请求是无状态的,有以下几个好处:

  • 客户端可以缓存数据来改进性能
  • 在接口层帮助系统分层解耦,限制复杂度,底层可以更加独立
  • 易于扩展,没有机器间关联

API 总览

根据 RESTful 的风格,接口中不应该出现动词,而利用 GET/PUT/POST/DELETE 来进行具体的动作,对应到『一起看飞机』这个项目,API 总览:

  • /api/position 客户端每隔一段调用一次该接口
    • POST 上传当前位置到服务器(状态变为上线)
    • DELETE 结束上传位置(状态变为下线,或通过超时判断)
  • /api/weather
    • GET 获取用户附近的天气状况
  • /api/flight
    • GET 获取用户附近的航班情况
  • /api/near
    • GET 获取用户附近的人

调试用接口

  • /debug/position
    • POST 获取指定用户的当前位置
  • /debug/weather
    • GET 获取指定位置附近的天气状况
  • /debug/flight
    • GET 获取指定位置附近的航班
  • /debug/near
    • POST 获取用户或指定位置附近的人
  • /debug/upload
    • POST 测试 json 数据上传

这里因为时间关系,只用非常简单粗暴的方式实现了 debug 接口。所有的数据处理都在 controller 层完成,并未涉及任何 model(暂时还不需要)

Debug 接口

坐标采用 WGS84 标准,不同的标准有很多,参考来源

  • WGS84 坐标系:即地球坐标系,国际上通用的坐标系。设备一般包含GPS芯片或者北斗芯片获取的经纬度为 WGS84 地理坐标系, 谷歌地图采用的是 WGS84 地理坐标系(中国范围除外)
  • GCJ02 坐标系:即火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由 WGS84 坐标系经加密后的坐标系。谷歌中国地图和搜搜中国地图采用的是 GCJ02 地理坐标系
  • BD09坐标系:即百度坐标系,GCJ02 坐标系经加密后的坐标系; 搜狗坐标系、图吧坐标系等,估计也是在 GCJ02 基础上加密而成的

另一个坐标转换的项目

这里因为时间关系,只用非常简单粗暴的方式实现了 debug 接口。所有的数据处理都在 controller 层完成,并未涉及任何 model(暂时还不需要)

Debug 接口主要用于内部测试,对于参数的传递要求比较灵活,主要是基于地理位置进行测试,输入除 position 外都是经纬度坐标

  • /debug (GET) 连接测试函数
  • /debug/position (POST) 获取指定用户的当前位置
  • /debug/weather (GET) 获取指定位置附近的天气状况
  • /debug/flight (GET) 获取指定位置附近的航班
  • /debug/near (POST) 获取指定位置附近的航班
  • /debug/upload (POST) 测试复杂数据上传

数据库设计

设计数据库之前,我们先要做一些准备工作:

  • 安装 Go 的数据库驱动 go get github.com/go-sql-driver/mysql
  • 连接到测试用数据库 mysql -h[host ip] -P[host port] -u[user name] -p[password]
  • 查看已有的数据库 show databases;
  • 新建数据库 create database wptdb
  • 使用该数据库 use wptdb
  • 清空某表 truncate table history

因为我们需要记录在线的人,所以得要一个表,叫做 current;另外我们需要记录历史纪录,所以需要另外一个表,叫做 history,这两个表的差别在于其中一个会对数据进行修改和删除,另一个则不会。

因为功能不同,这两个表的设计也不尽相同(主要是主键的选择)。current 表需要经常更新,history 表更多是记录轨迹和时间。

Current 表的初步设计,这里在基本信息中加入了一个 region 字段,用来标识所在分区,这样在检索的时候可以极大提高效率。为什么要用 BIGINT 这里说一下。经度范围 0-360,维度为 0-180,如果精确到 0.001 的话,就是
36000*18000=648,000,000,加上为高度预留的 3 位,就是 12 位,是超过 INT 所能表示的大小的。时间戳为 UNIX 时间

CREATE TABLE `current` (
`uid` INT(10) NOT NULL,
`latitude` DOUBLE NOT NULL,
`longitude` DOUBLE NOT NULL,
`altitude` DOUBLE NOT NULL,
`timestamp` INT(10) NOT NULL,
`region` BIGINT(12) NOT NULL,
`online` TINYINT NOT NULL,
PRIMARY KEY (`uid`)
);

History 表的初步设计主要是保证每条数据的唯一性(也就是主键),以及同一个 id 的数据要尽可能放在一起方便检索。这里的 actionid 是由 uidtimestamp 拼接而成的。而 location 是由 经纬度和高度拼接而成的。

CREATE TABLE `history`(
`actionid` CHAR(32) NOT NULL,
`location` VARCHAR(64) NOT NULL,
`timestamp` INT(10) NOT NULL,
PRIMARY KEY (`actionid`)
);

框架搭建

借助 beego,对于框架部分我们需要做的不多,主要就是配置好路由和对应的 Controller,具体可以参见代码。简单来说,就是新建一个 struct 包含 beego.Controller,然后对应写函数,并在路由中注册,比如:

// router.go
package routers
import (
"watch-plane-together/controllers"
"github.com/astaxie/beego"
)
func init() {
beego.Router("/", &controllers.MainController{})
beego.Router("/debug/", &controllers.DebugController{})
beego.Router("/debug/position", &controllers.DebugController{}, "post:DebugPosition")
beego.Router("/debug/weather", &controllers.DebugController{}, "get:DebugWeather")
beego.Router("/debug/flight", &controllers.DebugController{}, "get:DebugFlight")
beego.Router("/debug/near", &controllers.DebugController{}, "post:DebugNear")
beego.Router("/debug/upload", &controllers.DebugController{}, "post:DebugUpload")
}

我们只需要在 debug.go 中对应编写 DebugPosition, DebugWeather, DebugFlightDebugUpload 方法即可。这里需要注意,附近的人具体的算法没有实现,会在之后专门介绍。

测试部署

接口测试可以使用 Chrome 插件 Postman,部署的话,因为 Go 直接静态编译,扔到服务器上运行即可,或者参考这里

总结

因为 beego 的缘故,其实只需要不到 200 行代码就可以完成基本的 demo 后台搭建,后面的 mvc 封装等等可以随着项目进行具体调整,作为一个简单的展示大约是足够的,进一步的学习就需要多多阅读源码,真正用 Go 的思路来写代码了。

参考链接

捧个钱场?