提交 70c8a556 编写于 作者: A astaxie

Merge pull request #132 from chenwenli/master

review three file for 13 chapter ,updated some word
# 13.1 项目规划 # 13.1 项目规划
做任何事情都需要做好规划,那么我们在开发博客系统之前,也需要多好项目的规划,如何设置目录结构,如何理解整个的流程图,当我们理解了应用的执行过程,那么接下来的设计编码就会变得相对容易了 做任何事情都需要做好规划,那么我们在开发博客系统之前,同样需要做好项目的规划,如何设置目录结构,如何理解整个项目的流程图,当我们理解了应用的执行过程,那么接下来的设计编码就会变得相对容易了
## gopath以及项目设置 ## gopath以及项目设置
gopath是一个文件系统的目录,我们可以随便设置一个目录为gopath,前面介绍过gopath可以是多个目录,在window系统设置环境变量,在linux/MacOS系统只要`export gopath=/home/astaxie/gopath`,但是必须保证gopath目录下面有三个目录pkg、bin、src目录。我们新建的项目放在src目录下面,我们暂定我们的博客目录叫做beeblog,下面是我在window下的环境变量和目录结构的截图: 假设指定gopath是文件系统的普通目录名,当然我们可以随便设置一个目录名,然后将其路径存入GOPATH。前面介绍过GOPATH可以是多个目录:在window系统设置环境变量;在linux/MacOS系统只要输入终端命令`export gopath=/home/astaxie/gopath`,但是必须保证gopath这个代码目录下面有三个目录pkg、bin、src。新建项目的源码放在src目录下面,现在暂定我们的博客目录叫做beeblog,下面是在window下的环境变量和目录结构的截图:
![](images/13.1.gopath.png?raw=true) ![](images/13.1.gopath.png?raw=true)
![](images/13.1.gopath2.png?raw=true) ![](images/13.1.gopath2.png?raw=true)
## 应用程序流程图 ## 应用程序流程图
博客系统是基于模型-视图-控制器这一设计模式的。MVC是一种将应用程序的逻辑层和表现层进行分离的方法。在实践中,由于表现层从Go中分离了出来,所以它允许你的网页中只包含很少的脚本。 博客系统是基于模型-视图-控制器这一设计模式的。MVC是一种将应用程序的逻辑层和表现层进行分离的结构方式。在实践中,由于表现层从Go中分离了出来,所以它允许你的网页中只包含很少的脚本。
- 模型 (Model) 代表你的数据结构。通常来说,你的模型类将包含取出、插入、更新你的数据库资料这些功能。 - 模型 (Model) 代表数据结构。通常来说,模型类将包含取出、插入、更新数据库资料等这些功能。
- 视图 (View) 是展示给用户的信息。一个视图通常是一个网页,但是在Go中,一个视图也可以是一个页面片段,如页头、页尾。它还可以是一个 RSS 页面,或任何其它类型的“页面”,Go实现的template包已经很好的体现了View这个概念 - 视图 (View) 是展示给用户的信息的结构及样式。一个视图通常是一个网页,但是在Go中,一个视图也可以是一个页面片段,如页头、页尾。它还可以是一个 RSS 页面,或其它类型的“页面”,Go实现的template包已经很好的实现了View层中的部分功能
- 控制器 (Controller) 是模型、视图以及其他任何处理HTTP请求所必须的资源之间的中介,并生成网页。 - 控制器 (Controller) 是模型、视图以及其他任何处理HTTP请求所必须的资源之间的中介,并生成网页。
下图设计了我们接下来的博客系统数据流如何贯穿整个系统: 下图显示了项目设计中博客系统的数据流是如何贯穿整个系统:
![](images/13.1.flow.png?raw=true) ![](images/13.1.flow.png?raw=true)
1. main.go作为应用入口,初始化运行博客所需要的基本资源,配置信息,监听端口。 1. main.go作为应用入口,初始化一些运行博客所需要的基本资源,配置信息,监听端口。
2. 路由功能检查HTTP请求,根据URL以及method来确定谁来处理请求 2. 路由功能检查HTTP请求,根据URL以及method来确定谁(控制层)来处理请求的转发资源
3. 如果缓存文件存在,它将绕过通常的系统执行顺序,被直接发送给浏览器。 3. 如果缓存文件存在,它将绕过通常的流程执行,被直接发送给浏览器。
4. 安全检测:应用程序控制器调用之前,HTTP请求和任用户提交的数据将被过滤。 4. 安全检测:应用程序控制器调用之前,HTTP请求和任用户提交的数据将被过滤。
5. 控制器装载模型、核心库、辅助函数,以及任何处理特定请求所需的其它资源,控制器主要处理业务逻辑。 5. 控制器装载模型、核心库、辅助函数,以及任何处理特定请求所需的其它资源,控制器主要负责处理业务逻辑。
6. 输出视图用来渲染需要发送到Web浏览器中的内容。如果开启缓存,视图首先被缓存,所以将可用于以后的请求。 6. 输出视图层中渲染好的即将发送到Web浏览器中的内容。如果开启缓存,视图首先被缓存,将用于以后的常规请求。
## 目录结构 ## 目录结构
根据上面的应用程序流程设计,博客的目录结构设计如下: 根据上面的应用程序流程设计,博客的目录结构设计如下:
...@@ -37,11 +37,11 @@ gopath是一个文件系统的目录,我们可以随便设置一个目录为go ...@@ -37,11 +37,11 @@ gopath是一个文件系统的目录,我们可以随便设置一个目录为go
|——views 视图库 |——views 视图库
## 框架设计 ## 框架设计
为了实现博客的快速搭建,打算基于上面的流程设计开发一个最小化的框架,框架包括路由功能、支持REST的控制器、自动化的模板渲染,日志系统、配置管理。 为了实现博客的快速搭建,打算基于上面的流程设计开发一个最小化的框架,框架包括路由功能、支持REST的控制器、自动化的模板渲染,日志系统、配置管理
## 总结 ## 总结
本小节介绍了博客系统从设置gopath到目录建立这样的基础信息,也介绍了将要采用的MVC模式,博客系统中数据流的执行流程,最后通过这些流程设计了博客系统的目录结构,至此我们完成了一个大框架的搭建,接下来的几个小节我们将会逐个实现。 本小节介绍了博客系统从设置GOPATH到目录建立这样的基础信息,也简单介绍了框架结构采用的MVC模式,博客系统中数据流的执行流程,最后通过这些流程设计了博客系统的目录结构,至此,我们基本完成一个框架的搭建,接下来的几个小节我们将会逐个实现。
## links ## links
* [目录](<preface.md>) * [目录](<preface.md>)
* 上一章: [构建博客系统](<13.md>) * 上一章: [构建博客系统](<13.md>)
* 下一节: [自定义路由器设计](<13.2.md>) * 下一节: [自定义路由器设计](<13.2.md>)
\ No newline at end of file
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
## HTTP路由 ## HTTP路由
HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个struct的方法),如前面小节所描述的结构图,路由在框架中相当于一个事件处理器,而这个事件包括: HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个struct的方法),如前面小节所描述的结构图,路由在框架中相当于一个事件处理器,而这个事件包括:
- 用户请求的路径(例如:/user/123,/article/123),当然还有查询串信息(例如?id=11) - 用户请求的路径(path)(例如:/user/123,/article/123),当然还有查询串信息(例如?id=11)
- HTTP的请求method(GET、POST、PUT、DELETE、PATCH等) - HTTP的请求方法(method)(GET、POST、PUT、DELETE、PATCH等)
路由器就是根据用户请求的这个信息定位到相应的处理函数 路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)
## 默认的路由实现 ## 默认的路由实现
在3.4小节有过介绍Go的http包的详解,里面介绍了Go的http包如何设计和实现路由,这里继续以一个例子来说明: 在3.4小节有过介绍Go的http包的详解,里面介绍了Go的http包如何设计和实现路由,这里继续以一个例子来说明:
...@@ -22,14 +22,14 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st ...@@ -22,14 +22,14 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st
log.Fatal(http.ListenAndServe(":8080", nil)) log.Fatal(http.ListenAndServe(":8080", nil))
上面的例子调用了http默认的DefaultServeMux来添加路由,两个参数,第一个参数是前面所讲的用户请求的路径(Go中保存在r.URL.Path),第二参数是定位要执行的函数,路由的思路主要集中在两点: 上面的例子调用了http默认的DefaultServeMux来添加路由,需要提供两个参数,第一个参数是希望用户访问此资源的URL路径(保存在r.URL.Path),第二参数是即将要执行的函数,以提供用户访问的资源。路由的思路主要集中在两点:
- 添加路由信息 - 添加路由信息
- 根据用户请求转发到要执行的函数 - 根据用户请求转发到要执行的函数
Go默认的添加是通过函数`http.Handle``http.HandleFunc`等来添加,底层都是调用了`DefaultServeMux.Handle(pattern string, handler Handler)`,这个函数会把路由信息存储在一个map信息中`map[string]muxEntry`,这就解决了上面说的第一点。 Go默认的路由添加是通过函数`http.Handle``http.HandleFunc`等来添加,底层都是调用了`DefaultServeMux.Handle(pattern string, handler Handler)`,这个函数会把路由信息存储在一个map信息中`map[string]muxEntry`,这就解决了上面说的第一点。
Go的监听端口,然后接收到tcp连接会扔给Handler来处理,上面的例子默认nil即为`http.DefaultServeMux`,通过`DefaultServeMux.ServeHTTP`函数来进行调度,循环上面存储的map信息,和访问url进行比对查询注册的处理函数,这样就实现了上面所说的第二点。 Go监听端口,然后接收到tcp连接会扔给Handler来处理,上面的例子默认nil即为`http.DefaultServeMux`,通过`DefaultServeMux.ServeHTTP`函数来进行调度,遍历之前存储的map路由信息,和用户访问的URL进行匹配,以查询对应注册的处理函数,这样就实现了上面所说的第二点。
for k, v := range mux.m { for k, v := range mux.m {
if !pathMatch(k, path) { if !pathMatch(k, path) {
...@@ -43,13 +43,13 @@ Go的监听端口,然后接收到tcp连接会扔给Handler来处理,上面 ...@@ -43,13 +43,13 @@ Go的监听端口,然后接收到tcp连接会扔给Handler来处理,上面
## beego框架路由实现 ## beego框架路由实现
目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是默认的路由器有几个限制点 目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是Go自带的路由器有几个限制
- 不支持参数设定,例如/user/:uid 这种泛类型匹配 - 不支持参数设定,例如/user/:uid 这种泛类型匹配
- 无法很好的支持REST模式,无法限制访问的方法,例如上面的例子中,用户访问/foo,可以用GET、POST、DELETE、HEAD等方式访问 - 无法很好的支持REST模式,无法限制访问的方法,例如上面的例子中,用户访问/foo,可以用GET、POST、DELETE、HEAD等方式访问
- 默认的路由规则太多了,我前面自己开发了一个API的应用,路由规则有三十几条,这种路由多了之后其实可以进一步简化,通过struct的方法进行一种简化 - 一般网站的路由规则太多了,编写繁琐。我前面自己开发了一个API应用,路由规则有三十几条,这种路由多了之后其实可以进一步简化,通过struct的方法进行一种简化
beego框架的路由器基于上面的几点限制考虑设计了一种REST方式的路由实现,路由设计也是基于上面默认设计的两点来考虑:存储路由和转发路由 beego框架的路由器基于上面的几点限制考虑设计了一种REST方式的路由实现,路由设计也是基于上面Go默认设计的两点来考虑:存储路由和转发路由
### 存储路由 ### 存储路由
针对前面所说的限制点,我们首先要解决参数支持就需要用到正则,第二和第三点我们通过一种变通的方法来解决,REST的方法对应到struct的方法中去,然后路由到struct而不是函数,这样在转发路由的时候就可以根据method来执行不同的方法。 针对前面所说的限制点,我们首先要解决参数支持就需要用到正则,第二和第三点我们通过一种变通的方法来解决,REST的方法对应到struct的方法中去,然后路由到struct而不是函数,这样在转发路由的时候就可以根据method来执行不同的方法。
...@@ -74,7 +74,7 @@ ControllerRegistor对外的接口函数有 ...@@ -74,7 +74,7 @@ ControllerRegistor对外的接口函数有
详细的实现如下所示: 详细的实现如下所示:
func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) { func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
parts := strings.Split(pattern, "/") parts := strings.Split(pattern, "/")
j := 0 j := 0
...@@ -82,8 +82,10 @@ ControllerRegistor对外的接口函数有 ...@@ -82,8 +82,10 @@ ControllerRegistor对外的接口函数有
for i, part := range parts { for i, part := range parts {
if strings.HasPrefix(part, ":") { if strings.HasPrefix(part, ":") {
expr := "([^/]+)" expr := "([^/]+)"
//a user may choose to override the defult expression //a user may choose to override the defult expression
// similar to expressjs: ‘/user/:id([0-9]+)’ // similar to expressjs: ‘/user/:id([0-9]+)’
if index := strings.Index(part, "("); index != -1 { if index := strings.Index(part, "("); index != -1 {
expr = part[index:] expr = part[index:]
part = part[:index] part = part[:index]
...@@ -96,9 +98,11 @@ ControllerRegistor对外的接口函数有 ...@@ -96,9 +98,11 @@ ControllerRegistor对外的接口函数有
//recreate the url pattern, with parameters replaced //recreate the url pattern, with parameters replaced
//by regular expressions. then compile the regex //by regular expressions. then compile the regex
pattern = strings.Join(parts, "/") pattern = strings.Join(parts, "/")
regex, regexErr := regexp.Compile(pattern) regex, regexErr := regexp.Compile(pattern)
if regexErr != nil { if regexErr != nil {
//TODO add error handling here to avoid panic //TODO add error handling here to avoid panic
panic(regexErr) panic(regexErr)
return return
...@@ -116,7 +120,7 @@ ControllerRegistor对外的接口函数有 ...@@ -116,7 +120,7 @@ ControllerRegistor对外的接口函数有
} }
### 静态路由实现 ### 静态路由实现
上面我们实现的动态路由的实现,Go的http包默认支持静态文件处理FileServer,由于我们实现了自定义的路由器,那么静态文件也需要自己设定,beego的静态文件夹保存在全局变量StaticDir中,StaticDir是一个map类型,实现如下: 上面我们实现的动态路由的实现,Go的http包默认支持静态文件处理FileServer,由于我们实现了自定义的路由器,那么静态文件也需要自己设定,beego的静态文件夹路径保存在全局变量StaticDir中,StaticDir是一个map类型,实现如下:
func (app *App) SetStaticPath(url string, path string) *App { func (app *App) SetStaticPath(url string, path string) *App {
StaticDir[url] = path StaticDir[url] = path
...@@ -129,7 +133,7 @@ ControllerRegistor对外的接口函数有 ...@@ -129,7 +133,7 @@ ControllerRegistor对外的接口函数有
### 转发路由 ### 转发路由
转发路由是基于ControllerRegistor的路由信息来进行转发的,详细的实现如下代码所示: 转发路由是基于ControllerRegistor的路由信息来进行转发的,详细的实现如下代码所示:
// AutoRoute // AutoRoute
func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...@@ -257,4 +261,4 @@ ControllerRegistor对外的接口函数有 ...@@ -257,4 +261,4 @@ ControllerRegistor对外的接口函数有
## links ## links
* [目录](<preface.md>) * [目录](<preface.md>)
* 上一章: [数据库设计](<13.2.md>) * 上一章: [数据库设计](<13.2.md>)
* 下一节: [controller设计](<13.4.md>) * 下一节: [controller设计](<13.4.md>)
\ No newline at end of file
# 13.3 controller设计 # 13.3 controller设计
传统的MVC框架大多数是基于Action设计的后缀式映射,然而目前流行的Web趋势是REST风格的架构。尽管使用Filter或者rewrite能够通过URL重写实现REST风格的URL,但是为什么不直接设计一个全新的REST风格的 MVC框架呢?本小节就是基于这种思路来讲述如何从头设计一个基于REST风格的MVC框架中的controller,最大限度地简化Web应用的开发,您甚至编写一行代码就可以实现“Hello, world”。 传统的MVC框架大多数是基于Action设计的后缀式映射,然而,现在Web流行REST风格的架构。尽管使用Filter或者rewrite能够通过URL重写实现REST风格的URL,但是为什么不直接设计一个全新的REST风格的 MVC框架呢?本小节就是基于这种思路来讲述如何从头设计一个基于REST风格的MVC框架中的controller,最大限度地简化Web应用的开发,甚至编写一行代码就可以实现“Hello, world”。
## controller作用 ## controller作用
MVC设计模式是目前Web应用开发中最常见的一种架构模式,通过分离 Model(模型)、View(视图)和 Controller(控制器),可以更容易实现易于扩展的UI。Model指后台返回的数据;View指需要渲染的页面,通常是模板页面,渲染后的结果通常是HTML;Controller指Web开发人员编写的处理不同URL的控制器,如前面小节讲述的路由就是转发到控制器的过程,controller在整个的MVC框架中起到了一个核心的作用,处理业务逻辑,因此控制器是整个框架中必不可少的一部分,Model和View会根据不同的业务可以不写,例如没有数据处理的逻辑处理,没有页面输出的302调整之类的就不需要Model和View,但是controller是必不可少的。 MVC设计模式是目前Web应用开发中最常见的架构模式,通过分离 Model(模型)、View(视图)和 Controller(控制器),可以更容易实现易于扩展的用户界面(UI)。Model指后台返回的数据;View指需要渲染的页面,通常是模板页面,渲染后的内容通常是HTML;Controller指Web开发人员编写的处理不同URL的控制器,如前面小节讲述的路由就是URL请求转发到控制器的过程,controller在整个的MVC框架中起到了一个核心的作用,负责处理业务逻辑,因此控制器是整个框架中必不可少的一部分,Model和View对于有些业务需求是可以不写的,例如没有数据处理的逻辑处理,没有页面输出的302调整之类的就不需要Model和View,但是controller这一环节是必不可少的。
## beego的REST设计 ## beego的REST设计
前面小节介绍了路由实现了注册struct的功能,而struct中实现了REST方式,因此我们需要设计一个用于逻辑处理controller的基类,这里主要设计了两个类型,一个struct、一个interface 前面小节介绍了路由实现了注册struct的功能,而struct中实现了REST方式,因此我们需要设计一个用于逻辑处理controller的基类,这里主要设计了两个类型,一个struct、一个interface
...@@ -32,7 +32,7 @@ MVC设计模式是目前Web应用开发中最常见的一种架构模式,通 ...@@ -32,7 +32,7 @@ MVC设计模式是目前Web应用开发中最常见的一种架构模式,通
Render() error //执行完method对应的方法之后渲染页面 Render() error //执行完method对应的方法之后渲染页面
} }
那么前面介绍的路由add的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法: 那么前面介绍的路由add函数的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法:
func (c *Controller) Init(ct *Context, cn string) { func (c *Controller) Init(ct *Context, cn string) {
c.Data = make(map[interface{}]interface{}) c.Data = make(map[interface{}]interface{})
...@@ -113,7 +113,7 @@ MVC设计模式是目前Web应用开发中最常见的一种架构模式,通 ...@@ -113,7 +113,7 @@ MVC设计模式是目前Web应用开发中最常见的一种架构模式,通
c.Ct.Redirect(code, url) c.Ct.Redirect(code, url)
} }
上面的controller基类完成了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下的函数 上面的controller基类已经实现了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下
Init() 初始化 Init() 初始化
Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数 Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数
...@@ -160,4 +160,4 @@ index.tpl的代码如下所示,我们可以看到数据的设置和显示都 ...@@ -160,4 +160,4 @@ index.tpl的代码如下所示,我们可以看到数据的设置和显示都
## links ## links
* [目录](<preface.md>) * [目录](<preface.md>)
* 上一章: [自定义路由器设计](<13.2.md>) * 上一章: [自定义路由器设计](<13.2.md>)
* 下一节: [日志和配置设计](<13.4.md>) * 下一节: [日志和配置设计](<13.4.md>)
\ No newline at end of file
文件模式从 100755 更改为 100644
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册