diff --git a/13.1.md b/13.1.md index ced462af..5c79cb71 100644 --- a/13.1.md +++ b/13.1.md @@ -1,29 +1,29 @@ # 13.1 项目规划 -做任何事情都需要做好规划,那么我们在开发博客系统之前,也需要多好项目的规划,如何设置目录结构,如何理解整个的流程图,当我们理解了应用的执行过程,那么接下来的设计编码就会变得相对容易了 +做任何事情都需要做好规划,那么我们在开发博客系统之前,同样需要做好项目的规划,如何设置目录结构,如何理解整个项目的流程图,当我们理解了应用的执行过程,那么接下来的设计编码就会变得相对容易了 ## 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.gopath2.png?raw=true) ## 应用程序流程图 -博客系统是基于模型-视图-控制器这一设计模式的。MVC是一种将应用程序的逻辑层和表现层进行分离的方法。在实践中,由于表现层从Go中分离了出来,所以它允许你的网页中只包含很少的脚本。 +博客系统是基于模型-视图-控制器这一设计模式的。MVC是一种将应用程序的逻辑层和表现层进行分离的结构方式。在实践中,由于表现层从Go中分离了出来,所以它允许你的网页中只包含很少的脚本。 -- 模型 (Model) 代表你的数据结构。通常来说,你的模型类将包含取出、插入、更新你的数据库资料这些功能。 -- 视图 (View) 是展示给用户的信息。一个视图通常是一个网页,但是在Go中,一个视图也可以是一个页面片段,如页头、页尾。它还可以是一个 RSS 页面,或任何其它类型的“页面”,Go实现的template包已经很好的体现了View这个概念。 +- 模型 (Model) 代表数据结构。通常来说,模型类将包含取出、插入、更新数据库资料等这些功能。 +- 视图 (View) 是展示给用户的信息的结构及样式。一个视图通常是一个网页,但是在Go中,一个视图也可以是一个页面片段,如页头、页尾。它还可以是一个 RSS 页面,或其它类型的“页面”,Go实现的template包已经很好的实现了View层中的部分功能。 - 控制器 (Controller) 是模型、视图以及其他任何处理HTTP请求所必须的资源之间的中介,并生成网页。 -下图设计了我们接下来的博客系统数据流如何贯穿整个系统: +下图显示了项目设计中博客系统的数据流是如何贯穿整个系统: ![](images/13.1.flow.png?raw=true) -1. main.go作为应用入口,初始化运行博客所需要的基本资源,配置信息,监听端口。 -2. 路由功能检查HTTP请求,根据URL以及method来确定谁来处理请求。 -3. 如果缓存文件存在,它将绕过通常的系统执行顺序,被直接发送给浏览器。 -4. 安全检测:应用程序控制器调用之前,HTTP请求和任何用户提交的数据将被过滤。 -5. 控制器装载模型、核心库、辅助函数,以及任何处理特定请求所需的其它资源,控制器主要处理业务逻辑。 -6. 输出视图用来渲染需要发送到Web浏览器中的内容。如果开启缓存,视图首先被缓存,所以将可用于以后的请求。 +1. main.go作为应用入口,初始化一些运行博客所需要的基本资源,配置信息,监听端口。 +2. 路由功能检查HTTP请求,根据URL以及method来确定谁(控制层)来处理请求的转发资源。 +3. 如果缓存文件存在,它将绕过通常的流程执行,被直接发送给浏览器。 +4. 安全检测:应用程序控制器调用之前,HTTP请求和任一用户提交的数据将被过滤。 +5. 控制器装载模型、核心库、辅助函数,以及任何处理特定请求所需的其它资源,控制器主要负责处理业务逻辑。 +6. 输出视图层中渲染好的即将发送到Web浏览器中的内容。如果开启缓存,视图首先被缓存,将用于以后的常规请求。 ## 目录结构 根据上面的应用程序流程设计,博客的目录结构设计如下: @@ -37,11 +37,11 @@ gopath是一个文件系统的目录,我们可以随便设置一个目录为go |——views 视图库 ## 框架设计 -为了实现博客的快速搭建,打算基于上面的流程设计开发一个最小化的框架,框架包括路由功能、支持REST的控制器、自动化的模板渲染,日志系统、配置管理。 +为了实现博客的快速搭建,打算基于上面的流程设计开发一个最小化的框架,框架包括路由功能、支持REST的控制器、自动化的模板渲染,日志系统、配置管理等。 ## 总结 -本小节介绍了博客系统从设置gopath到目录建立这样的基础信息,也介绍了将要采用的MVC模式,博客系统中数据流的执行流程,最后通过这些流程设计了博客系统的目录结构,至此我们完成了一个大框架的搭建,接下来的几个小节我们将会逐个实现。 +本小节介绍了博客系统从设置GOPATH到目录建立这样的基础信息,也简单介绍了框架结构采用的MVC模式,博客系统中数据流的执行流程,最后通过这些流程设计了博客系统的目录结构,至此,我们基本完成一个框架的搭建,接下来的几个小节我们将会逐个实现。 ## links * [目录]() * 上一章: [构建博客系统](<13.md>) - * 下一节: [自定义路由器设计](<13.2.md>) \ No newline at end of file + * 下一节: [自定义路由器设计](<13.2.md>) diff --git a/13.2.md b/13.2.md index a6988183..902c23cc 100644 --- a/13.2.md +++ b/13.2.md @@ -3,10 +3,10 @@ ## HTTP路由 HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个struct的方法),如前面小节所描述的结构图,路由在框架中相当于一个事件处理器,而这个事件包括: -- 用户请求的路径(例如:/user/123,/article/123),当然还有查询串信息(例如?id=11) -- HTTP的请求method(GET、POST、PUT、DELETE、PATCH等) +- 用户请求的路径(path)(例如:/user/123,/article/123),当然还有查询串信息(例如?id=11) +- HTTP的请求方法(method)(GET、POST、PUT、DELETE、PATCH等) -路由器就是根据用户请求的这个信息定位到相应的处理函数。 +路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)。 ## 默认的路由实现 在3.4小节有过介绍Go的http包的详解,里面介绍了Go的http包如何设计和实现路由,这里继续以一个例子来说明: @@ -22,14 +22,14 @@ HTTP路由组件负责将HTTP请求交到对应的函数处理(或者是一个st 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 { if !pathMatch(k, path) { @@ -43,13 +43,13 @@ Go的监听端口,然后接收到tcp连接会扔给Handler来处理,上面 ## beego框架路由实现 -目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是默认的路由器有几个限制点: +目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是Go自带的路由器有几个限制: - 不支持参数设定,例如/user/:uid 这种泛类型匹配 - 无法很好的支持REST模式,无法限制访问的方法,例如上面的例子中,用户访问/foo,可以用GET、POST、DELETE、HEAD等方式访问 -- 默认的路由规则太多了,我前面自己开发了一个API的应用,路由规则有三十几条,这种路由多了之后其实可以进一步简化,通过struct的方法进行一种简化 +- 一般网站的路由规则太多了,编写繁琐。我前面自己开发了一个API应用,路由规则有三十几条,这种路由多了之后其实可以进一步简化,通过struct的方法进行一种简化 -beego框架的路由器基于上面的几点限制考虑设计了一种REST方式的路由实现,路由设计也是基于上面的默认设计的两点来考虑:存储路由和转发路由 +beego框架的路由器基于上面的几点限制考虑设计了一种REST方式的路由实现,路由设计也是基于上面Go默认设计的两点来考虑:存储路由和转发路由 ### 存储路由 针对前面所说的限制点,我们首先要解决参数支持就需要用到正则,第二和第三点我们通过一种变通的方法来解决,REST的方法对应到struct的方法中去,然后路由到struct而不是函数,这样在转发路由的时候就可以根据method来执行不同的方法。 @@ -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, "/") j := 0 @@ -82,8 +82,10 @@ ControllerRegistor对外的接口函数有 for i, part := range parts { if strings.HasPrefix(part, ":") { expr := "([^/]+)" + //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 { expr = part[index:] part = part[:index] @@ -96,9 +98,11 @@ ControllerRegistor对外的接口函数有 //recreate the url pattern, with parameters replaced //by regular expressions. then compile the regex + pattern = strings.Join(parts, "/") regex, regexErr := regexp.Compile(pattern) if regexErr != nil { + //TODO add error handling here to avoid panic panic(regexErr) return @@ -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 { StaticDir[url] = path @@ -129,7 +133,7 @@ ControllerRegistor对外的接口函数有 ### 转发路由 -转发路由是基于ControllerRegistor的路由信息来进行转发的,详细的实现如下代码所示: +转发路由是基于ControllerRegistor里的路由信息来进行转发的,详细的实现如下代码所示: // AutoRoute func (p *ControllerRegistor) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -257,4 +261,4 @@ ControllerRegistor对外的接口函数有 ## links * [目录]() * 上一章: [数据库设计](<13.2.md>) - * 下一节: [controller设计](<13.4.md>) \ No newline at end of file + * 下一节: [controller设计](<13.4.md>) diff --git a/13.3.md b/13.3.md index 5997c640..84304bc5 100644 --- a/13.3.md +++ b/13.3.md @@ -1,9 +1,9 @@ # 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作用 -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设计 前面小节介绍了路由实现了注册struct的功能,而struct中实现了REST方式,因此我们需要设计一个用于逻辑处理controller的基类,这里主要设计了两个类型,一个struct、一个interface @@ -28,10 +28,11 @@ MVC设计模式是目前Web应用开发中最常见的一种架构模式,通 Head() //method=HEAD的处理 Patch() //method=PATCH的处理 Options() //method=OPTIONS的处理 - Finish() //执行完成之后的处理 Render() error //执行完method对应的方法之后渲染页面 + Finish() //执行完成之后的处理 + Render() error //执行完method对应的方法之后渲染页面 } -那么前面介绍的路由add的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法: +那么前面介绍的路由add函数的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法: func (c *Controller) Init(ct *Context, cn string) { c.Data = make(map[interface{}]interface{}) @@ -112,7 +113,7 @@ MVC设计模式是目前Web应用开发中最常见的一种架构模式,通 c.Ct.Redirect(code, url) } -上面的controller基类完成了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下的函数: +上面的controller基类已经实现了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下: Init() 初始化 Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数 @@ -159,4 +160,4 @@ index.tpl的代码如下所示,我们可以看到数据的设置和显示都 ## links * [目录]() * 上一章: [自定义路由器设计](<13.2.md>) - * 下一节: [日志和配置设计](<13.4.md>) \ No newline at end of file + * 下一节: [日志和配置设计](<13.4.md>)