├── en ├── 01.前言.md ├── 03.模板.md ├── 18.部署.md ├── 04.样式.md ├── 05.表单.md ├── 08.数据库.md ├── 13.分页.md ├── 09.用户注册登陆.md ├── 10.用户管理.md ├── 11.单元测试.md ├── 14.全文搜索.md ├── 15.邮件支持.md ├── 16.日期和时间.md ├── 06.ajax-单页应用.md ├── 07.国际化与本地化.md ├── 12. 粉丝-联系人-好友.md ├── 02.hello-beego.md ├── 17.调试-测试-性能分析.md ├── SUMMARY.md ├── README.md ├── api-authentication.md ├── using-gulp.md └── beego-in-docker.md ├── zh ├── 01.前言.md ├── 18.部署.md ├── 13.分页.md ├── 09.用户注册登陆.md ├── 10.用户管理.md ├── 11.单元测试.md ├── 14.全文搜索.md ├── 15.邮件支持.md ├── 16.日期和时间.md ├── 国际化与本地化.md ├── ajax-单页应用.md ├── 12. 粉丝-联系人-好友.md ├── 17.调试-测试-性能分析.md ├── SUMMARY.md ├── README.md ├── 02.hello-beego.md ├── 06.数据库.md ├── 07.应用配置.md ├── 05.CI-持续集成.md ├── 04.请求数据处理.md ├── 03.模板.md └── beego-in-docker.md ├── LANGS.md ├── images ├── docker.golang.png ├── docker.mysql.png ├── docker.bee.run.png ├── docker.swagger.png ├── 02 │ └── 01-first-beego.png ├── docker.link.hosts.png └── docker.golang.hello-world.png ├── .gitignore ├── README.md ├── medias └── beeblog.sql └── LICENSE /en/01.前言.md: -------------------------------------------------------------------------------- 1 | 1. 前言 2 | ========= 3 | -------------------------------------------------------------------------------- /en/03.模板.md: -------------------------------------------------------------------------------- 1 | 3. 模板 2 | ========= 3 | -------------------------------------------------------------------------------- /en/18.部署.md: -------------------------------------------------------------------------------- 1 | 18. 部署 2 | ========= 3 | -------------------------------------------------------------------------------- /zh/01.前言.md: -------------------------------------------------------------------------------- 1 | 1. 前言 2 | ========= 3 | -------------------------------------------------------------------------------- /zh/18.部署.md: -------------------------------------------------------------------------------- 1 | 18. 部署 2 | ========= 3 | -------------------------------------------------------------------------------- /en/04.样式.md: -------------------------------------------------------------------------------- 1 | 4. 样式 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/05.表单.md: -------------------------------------------------------------------------------- 1 | 5. 表单 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/08.数据库.md: -------------------------------------------------------------------------------- 1 | 8. 数据库 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/13.分页.md: -------------------------------------------------------------------------------- 1 | 13. 分页 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/13.分页.md: -------------------------------------------------------------------------------- 1 | 13. 分页 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /LANGS.md: -------------------------------------------------------------------------------- 1 | * [English](en/) 2 | * [中文](zh/) 3 | -------------------------------------------------------------------------------- /en/09.用户注册登陆.md: -------------------------------------------------------------------------------- 1 | 9. 用户注册登陆 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/10.用户管理.md: -------------------------------------------------------------------------------- 1 | 10. 用户管理 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/11.单元测试.md: -------------------------------------------------------------------------------- 1 | 11. 单元测试 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/14.全文搜索.md: -------------------------------------------------------------------------------- 1 | 14. 全文搜索 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/15.邮件支持.md: -------------------------------------------------------------------------------- 1 | 15. 邮件支持 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/16.日期和时间.md: -------------------------------------------------------------------------------- 1 | 16. 日期和时间 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/09.用户注册登陆.md: -------------------------------------------------------------------------------- 1 | 9. 用户注册登陆 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/10.用户管理.md: -------------------------------------------------------------------------------- 1 | 10. 用户管理 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/11.单元测试.md: -------------------------------------------------------------------------------- 1 | 11. 单元测试 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/14.全文搜索.md: -------------------------------------------------------------------------------- 1 | 14. 全文搜索 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/15.邮件支持.md: -------------------------------------------------------------------------------- 1 | 15. 邮件支持 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/16.日期和时间.md: -------------------------------------------------------------------------------- 1 | 16. 日期和时间 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/国际化与本地化.md: -------------------------------------------------------------------------------- 1 | 7. 国际化与本地化 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/06.ajax-单页应用.md: -------------------------------------------------------------------------------- 1 | 6. Ajax 单页应用 2 | ======== 3 | 4 | -------------------------------------------------------------------------------- /en/07.国际化与本地化.md: -------------------------------------------------------------------------------- 1 | 7. 国际化与本地化 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/ajax-单页应用.md: -------------------------------------------------------------------------------- 1 | 6. Ajax 单页应用 2 | ======== 3 | 4 | -------------------------------------------------------------------------------- /en/12. 粉丝-联系人-好友.md: -------------------------------------------------------------------------------- 1 | 12. 粉丝, 联系人, 好友 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/12. 粉丝-联系人-好友.md: -------------------------------------------------------------------------------- 1 | 12. 粉丝, 联系人, 好友 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/02.hello-beego.md: -------------------------------------------------------------------------------- 1 | 2. Hello Beego! 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /en/17.调试-测试-性能分析.md: -------------------------------------------------------------------------------- 1 | 17. 调试, 测试, 和性能分析 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /zh/17.调试-测试-性能分析.md: -------------------------------------------------------------------------------- 1 | 17. 调试, 测试, 和性能分析 2 | ========= 3 | 4 | -------------------------------------------------------------------------------- /images/docker.golang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lei-cao/beego-in-action/HEAD/images/docker.golang.png -------------------------------------------------------------------------------- /images/docker.mysql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lei-cao/beego-in-action/HEAD/images/docker.mysql.png -------------------------------------------------------------------------------- /images/docker.bee.run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lei-cao/beego-in-action/HEAD/images/docker.bee.run.png -------------------------------------------------------------------------------- /images/docker.swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lei-cao/beego-in-action/HEAD/images/docker.swagger.png -------------------------------------------------------------------------------- /images/02/01-first-beego.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lei-cao/beego-in-action/HEAD/images/02/01-first-beego.png -------------------------------------------------------------------------------- /images/docker.link.hosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lei-cao/beego-in-action/HEAD/images/docker.link.hosts.png -------------------------------------------------------------------------------- /images/docker.golang.hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lei-cao/beego-in-action/HEAD/images/docker.golang.hello-world.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | .idea 27 | /_book/ 28 | -------------------------------------------------------------------------------- /en/SUMMARY.md: -------------------------------------------------------------------------------- 1 | * Preface 2 | * Hello Beego! 3 | * Templates 4 | * Facelift 5 | * Web Forms 6 | * SAP; Ajax and Single Page Application 7 | * I18n and L10n 8 | * Database 9 | * User Logins 10 | * Profile Page And Avatars 11 | * Unit Testing 12 | * Followers, Contacts And Friends 13 | * Pagination 14 | * Full Text Search 15 | * Email Support 16 | * Dates and Times 17 | * Debugging, Testing and Profiling 18 | * Deployment -------------------------------------------------------------------------------- /zh/SUMMARY.md: -------------------------------------------------------------------------------- 1 | * [前言](01.前言.md) 2 | * [Hello Beego!](02.hello-beego.md) 3 | * [模板](03.模板.md) 4 | * [请求数据处理](04.请求数据处理.md) 5 | * [持续集成 Continuous Integration](05.CI-持续集成.md) 6 | * [数据库和 ORM](06.数据库.md) 7 | * [应用配置](07.应用配置.md) 8 | * [Ajax 单页应用] 9 | * [国际化与本地化] 10 | * [数据库] 11 | * [用户注册登陆] 12 | * [用户管理] 13 | * [单元测试] 14 | * [粉丝, 联系人, 好友] 15 | * [分页] 16 | * [全文搜索] 17 | * [邮件支持] 18 | * [日期和时间] 19 | * [调试, 测试, 和性能分析] 20 | * [部署] 21 | -------------------------------------------------------------------------------- /en/README.md: -------------------------------------------------------------------------------- 1 | Beego In Action 2 | ================== 3 | 4 | # Outline 5 | 6 | 1. Preface 7 | 2. Hello Beego! 8 | 3. Templates 9 | 4. Facelift 10 | 5. Web Forms 11 | 6. SAP; Ajax and Single Page Application 12 | 7. I18n and L10n 13 | 8. Database 14 | 9. User Logins 15 | 10. Profile Page And Avatars 16 | 11. Unit Testing 17 | 12. Followers, Contacts And Friends 18 | 13. Pagination 19 | 14. Full Text Search 20 | 15. Email Support 21 | 16. Dates and Times 22 | 17. Debugging, Testing and Profiling 23 | 18. Deployment 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Beego In Action 2 | 3 | [Beego](http://beego.me) is a framework in Golang, many companies are using Beego in their live products. If you are developing a 4 | web application, Golang and Beego are good choices. 5 | 6 | This book will teach you how to use Beego to develop a micro blog system, Beefer. You will learn many things about Beego in this book. 7 | This book is not only about Beego, but also about how to develop a real-world web application in the full-stack view. 8 | 9 | **推荐在 [Gitbook](https://lei-cao.gitbooks.io/beego-in-action/content/zh/index.html) 阅读, 有更好的阅读体验.** 10 | 11 | * [English](en/) 12 | * [简体中文](zh/) 13 | 14 | -------------------------------------------------------------------------------- /zh/README.md: -------------------------------------------------------------------------------- 1 | Beego 实战 2 | ================== 3 | 4 | [Beego](http://beego.me) 是 Golang 的一个框架, 有不少大公司的线上产品都在使用它, 用它来开发基于 Golang 的 Web 应用是一个不错的选择. 5 | 6 | 本书讲介绍如何使用 Beego 框架写一个轻博客应用 Beefer. 本书将通 Beefer 过这个完整的项目来介绍使用 Beego 做 Web 应用的方方面面. 本书并不只是简单的介绍 Beego, 7 | 而是讲以一个全栈开发的视角来介绍如何开发我们的 Beefer 应用. 8 | 9 | **推荐在 [Gitbook](https://lei-cao.gitbooks.io/beego-in-action/content/zh/index.html) 阅读, 有更好的阅读体验.** 10 | 11 | [目录](SUMMARY.md) 12 | 13 | * [前言](01.前言.md) 14 | * [Hello Beego!](02.hello-beego.md) 15 | * [模板](03.模板.md) 16 | * [请求数据处理](04.请求数据处理.md) 17 | * [持续集成 Continuous Integration](05.CI-持续集成.md) 18 | * [数据库和 ORM](06.数据库.md) 19 | * [应用配置](07.应用配置.md) 20 | * [Ajax 单页应用] 21 | * [国际化与本地化] 22 | * [数据库] 23 | * [用户注册登陆] 24 | * [用户管理] 25 | * [单元测试] 26 | * [粉丝, 联系人, 好友] 27 | * [分页] 28 | * [全文搜索] 29 | * [邮件支持] 30 | * [日期和时间] 31 | * [调试, 测试, 和性能分析] 32 | * [部署] 33 | 34 | -------------------------------------------------------------------------------- /zh/02.hello-beego.md: -------------------------------------------------------------------------------- 1 | 2. Hello Beego! 2 | ========= 3 | 4 | # 安装和配置 `Go` 环境 5 | 6 | 关于 `Go` 环境的搭建, Astaxie 的书 [Go Web 编程](https://github.com/astaxie/build-web-application-with-golang)中有详细的介绍, 7 | 具体可以参考[这里](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/01.0.md) 8 | 9 | # 安装 `beego` 和 `bee` 10 | 11 | 在配置好 `Go` 环境之后, 接下来我们要做的是安装 `beego` 以及 `beego` 的工具包 `bee`. 在命令行输入: 12 | 13 | ```zsh 14 | go get github.com/astaxie/beego 15 | go get github.com/beego/bee 16 | ``` 17 | 18 | 你可以通过输入 `bee version` 来检查 `beego` 和 `bee` 是否安装成功. 如果提示找不到 `bee` 命令, 你可能需要将 `$GOPATH/bin` 加入你的 `$PATH` 变量中. 19 | 20 | # Hello Beego 21 | 22 | 在安装了 `beego` 和 `bee` 之后, 你就可以开始写 `beego` 应用了. 先看看最简单的 "Hello World" 程序: 23 | 24 | 25 | 在你的 `GOPATH` 下面创建一个一个项目目录, 我创建的是 `$GOPATH/src/github.com/lei-cao/beefer`: 26 | 27 | ```zsh 28 | cd $GOPATH/src/github.com/lei-cao/ 29 | mkdir beefer 30 | touch beefer.go 31 | ``` 32 | 33 | 接着你可以用你习惯的编辑器来进行编辑, 我使用的是安装了 Golang 插件的 Intellij IDEA. 详细的安装请看[TODO: Intellij IDEA 配置]() 34 | 35 | 36 | 在前面创建的 `beefer.go` 文件中输入下面的代码: 37 | 38 | ```go 39 | package main 40 | 41 | import "github.com/astaxie/beego" 42 | 43 | func main() { 44 | beego.Run(":8085") 45 | } 46 | ``` 47 | 48 | 本段代码的 [commit][1] 和 [在线演示] [1.1] 49 | 50 | 接着在当前工作目录在 `beefer` 目录的命令行中输入 `go build`, `golang` 会将你的 `beefer.go` 程序编译成一个可执行文件 `beefer`, 51 | 接着你就可以输入 `./beefer` 来运行你的第一个 `beego` 程序了. 你会看到类似的结果: 52 | 53 | ![](https://github.com/lei-cao/beego-in-action/raw/master/images/02/01-first-beego.png?raw=true) 54 | 55 | 在运行 `./beefer` 之后, 你会看到一行日志信息显示 `[app.go:96] [I] http server Running on :8085`, 则证明 `beego` 已经开启了一个 56 | web 服务在 8085 端口, 通过 `beego.Run()` 的参数, 你可以修改监听的端口. 这时你可以通过浏览器来访问你的 `beefer` 应用了: [http://localhost:8085](http://localhost:8085). 57 | 58 | 这时你通过浏览器访问 `beefer` 应用, 它只是返回 "Page Not Found" 的 404 页面. 因为我们只是启动了 `beego` http 服务来监听 http 请求, 59 | 但是接到请求后我们还没有对它们进行处理, 接下来让我们开始处理我们接收到的 http 请求. 修改你的 `beefer.go` 60 | 61 | ```go 62 | package main 63 | 64 | import "github.com/astaxie/beego" 65 | 66 | type BeeferController struct { 67 | beego.Controller 68 | } 69 | 70 | func (c *BeeferController) Get() { 71 | c.Ctx.WriteString("Hello Beego") 72 | } 73 | 74 | func main() { 75 | beego.Router("/", &BeeferController{}) 76 | beego.Run(":8085") 77 | } 78 | ``` 79 | 80 | 本段代码的 [commit][2] 和 [在线演示] [2.2] 81 | 82 | 1. 这里我们创建了一个叫做 `BeeferController` 的控制器, 它内嵌了一个 `beego.Controller` 控制器. 83 | 84 | 2. 接着, 我们给 `BeeferController` 控制器 创建了 `Get()` 方法. 它是用来处理 http 请求中的 `GET` 方法的. 85 | 86 | 3. 在 `Get()` 方法中我们输出了 "Hello Beego" 到 http 响应的 body 中. 87 | 88 | 4. 在 `beego.Run()` 之前 我们通过 `beego.Router("/", &BeeferController{})` 注册了一个路由, 它将访问 "/" URI 的请求分发给 `BeeferController` 89 | 进行处理 90 | 91 | 再一次输入 `go build` 然后 `./beefer`, 然后访问 [http://localhost:8085](http://localhost:8085), 你就会在浏览器看到 "Hello Beego" 的结果了. 92 | 93 | 你会发现每次修改我们都要 `go build` 然后重启应用, 十分麻烦. 这时我们可以使用 `bee` 工具来帮我们解决这些麻烦, 只需要在 `/beefer` 目录下运行 `bee run` 94 | `bee` 就会在我们修改并保存文件后自动的编译并重启我们的应用, 省去了我们的麻烦. 95 | 96 | 97 | # 依赖管理 98 | 99 | 我们在在开发的过程中不可避免的会使用到第三方的依赖库. 对于我们的 `beefer` 应用, `beego` 就是我们的第一个依赖库. 我们希望我们的 `beefer` 足够健壮, 当第三方的库发生变化时不至于影响到我们. 100 | 101 | 如果我们每次都使用 `go get` 来获取依赖, 那么它只会去获取最新的版本, 很可能就和我们的应用不兼容了. 在 `beefer` 中我们会使用现在比较流行的 [godep](https://github.com/tools/godep) 进行依赖管理. 102 | 103 | 首先安装 `godep`: 104 | 105 | `go get github.com/tools/godep` 106 | 107 | 然后在 `beefer` 目录下执行: 108 | 109 | `godep save` 110 | 111 | 这时 `godep` 会将我们项目中的所有依赖复制到我们项目的 `Godeps` 目录下, 并创建 `Godeps.json` 来记录我们所有的依赖库和库的版本. 我们只需要根据项目中的依赖来编译应用. 112 | 113 | `godep` 的具体使用请参考其[文档](https://github.com/tools/godep). 114 | 115 | 最后我们将 `Godeps` 目录也加入 `git`. 116 | 117 | 118 | [1]: https://github.com/lei-cao/beefer/commit/71be281 119 | [1.1]: http://s02-1.lei-cao.com 120 | [2]: https://github.com/lei-cao/beefer/commit/9558f56 121 | [2.2]: http://s02-2.lei-cao.com 122 | 123 | -------------------------------------------------------------------------------- /medias/beeblog.sql: -------------------------------------------------------------------------------- 1 | # ************************************************************ 2 | # Sequel Pro SQL dump 3 | # Version 4096 4 | # 5 | # http://www.sequelpro.com/ 6 | # http://code.google.com/p/sequel-pro/ 7 | # 8 | # Host: 127.0.0.1 (MySQL 5.6.13-debug) 9 | # Database: beeblog 10 | # Generation Time: 2014-09-27 09:35:46 +0000 11 | # ************************************************************ 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8 */; 18 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 19 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 20 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 21 | 22 | 23 | # Dump of table comments 24 | # ------------------------------------------------------------ 25 | 26 | DROP TABLE IF EXISTS `comments`; 27 | 28 | CREATE TABLE `comments` ( 29 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 30 | `comment` text NOT NULL, 31 | `commenter_id` int(11) unsigned NOT NULL, 32 | `post_id` int(11) unsigned NOT NULL, 33 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 34 | PRIMARY KEY (`id`), 35 | KEY `fk_comments_users` (`commenter_id`), 36 | KEY `fk_comments_posts` (`post_id`), 37 | CONSTRAINT `fk_comments_posts` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, 38 | CONSTRAINT `fk_comments_users` FOREIGN KEY (`commenter_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 40 | 41 | LOCK TABLES `comments` WRITE; 42 | /*!40000 ALTER TABLE `comments` DISABLE KEYS */; 43 | 44 | INSERT INTO `comments` (`id`, `comment`, `commenter_id`, `post_id`, `created_at`) 45 | VALUES 46 | (1,'An awesome post.',1,1,'2014-09-27 17:26:51'); 47 | 48 | /*!40000 ALTER TABLE `comments` ENABLE KEYS */; 49 | UNLOCK TABLES; 50 | 51 | 52 | # Dump of table posts 53 | # ------------------------------------------------------------ 54 | 55 | DROP TABLE IF EXISTS `posts`; 56 | 57 | CREATE TABLE `posts` ( 58 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 59 | `title` varchar(256) NOT NULL DEFAULT '', 60 | `text` text NOT NULL, 61 | `owner_id` int(11) unsigned NOT NULL, 62 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 63 | PRIMARY KEY (`id`), 64 | KEY `fk_posts_users` (`owner_id`), 65 | CONSTRAINT `fk_posts_users` FOREIGN KEY (`owner_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE 66 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 67 | 68 | LOCK TABLES `posts` WRITE; 69 | /*!40000 ALTER TABLE `posts` DISABLE KEYS */; 70 | 71 | INSERT INTO `posts` (`id`, `title`, `text`, `owner_id`, `created_at`) 72 | VALUES 73 | (1,'Create a documented CRUD API application with one command ',' o',1,'2014-09-27 17:23:42'); 74 | 75 | /*!40000 ALTER TABLE `posts` ENABLE KEYS */; 76 | UNLOCK TABLES; 77 | 78 | 79 | # Dump of table users 80 | # ------------------------------------------------------------ 81 | 82 | DROP TABLE IF EXISTS `users`; 83 | 84 | CREATE TABLE `users` ( 85 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 86 | `username` varchar(64) NOT NULL DEFAULT '', 87 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 88 | PRIMARY KEY (`id`) 89 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 90 | 91 | LOCK TABLES `users` WRITE; 92 | /*!40000 ALTER TABLE `users` DISABLE KEYS */; 93 | 94 | INSERT INTO `users` (`id`, `username`, `created_at`) 95 | VALUES 96 | (1,'beego','2014-09-27 16:32:49'); 97 | 98 | /*!40000 ALTER TABLE `users` ENABLE KEYS */; 99 | UNLOCK TABLES; 100 | 101 | 102 | 103 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 104 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 105 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 106 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 107 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 108 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 109 | -------------------------------------------------------------------------------- /zh/06.数据库.md: -------------------------------------------------------------------------------- 1 | 6. 数据库和 ORM 2 | ========= 3 | 4 | 我们在前面的 [请求数据处理] [1] 中接受了用户的输入信息, 创建了用户账号, 并存在了 `session` 中. 但是一旦 `session` 过期, 用户信息就会随之丢失. 我们还需要将这些信息进行持久化存储, 这就要用到数据库. 5 | 6 | 要和数据库进行交互, `beego` 提供了 [ORM 模块](http://beego.me/docs/mvc/model/overview.md) 方便我们完成常用的数据库操纵. 它支持 `MySql`, `PostgreSQL`, `Sqlite3` 三种数据库. 7 | 8 | # ORM 对象 9 | 10 | 要使用 ORM, 我们首先来扩展 `User` 对象: 11 | 12 | ```go 13 | type User struct { 14 | Id int 15 | Username string `orm:"size(50)"` 16 | Email string `orm:"size(200)"` 17 | Password string `orm:"size(100)"` 18 | } 19 | ``` 20 | 21 | 这里的 `User` 将会映射到一张数据库的表, 我们新增了 `Id` 作为表的主键, 增加了 `Email` 和 `Password` 字段, 并通过 `orm:"size()"` 标签来设置字段在数据表中的长度. 22 | 23 | # ORM 同步数据库 24 | 25 | 接着我们需要通过定义的 ORM 对象来创建数据库结构, 这里我们可以选择最简便的 `sqlite` 数据库: 26 | 27 | ```go 28 | import ( 29 | "github.com/astaxie/beego/orm" 30 | _ "github.com/mattn/go-sqlite3" 31 | ) 32 | 33 | func init() { 34 | // set default database 35 | orm.RegisterDataBase("default", "sqlite3", "./beefer.db", 30) 36 | 37 | // register model 38 | orm.RegisterModel(new(User)) 39 | 40 | // create table 41 | orm.RunSyncdb("default", false, true) 42 | } 43 | ``` 44 | 45 | - 这里首先通过 `orm.RegisterDataBase("default", "sqlite3", "./beefer.db", 30)` 注册了一个 `sqlite` 数据库作为开发数据库. 46 | - 接着用 `orm.RegisterModel(new(User))` 来注册之前定义的对象 47 | - 最后用 `orm.RunSyncdb("default", false, true)` 来同步 ORM 对象和数据库 48 | 49 | 这时, 在你重启应用的时候, `beego` 便会自动帮你同步数据库: 50 | 51 | ```sql 52 | create table `user` 53 | -- -------------------------------------------------- 54 | -- Table Structure for `main.User` 55 | -- -------------------------------------------------- 56 | CREATE TABLE IF NOT EXISTS `user` ( 57 | `id` integer NOT NULL PRIMARY KEY AUTOINCREMENT, 58 | `username` varchar(50) NOT NULL, 59 | `email` varchar(200) NOT NULL, 60 | `password` varchar(100) NOT NULL 61 | ); 62 | ``` 63 | 64 | # 插入数据 65 | 66 | 在有了 ORM 对象和数据库之后, 接着就可以将之前注册的用户信息持久化的存储在数据库中: 67 | 68 | ```go 69 | user := &User{Username: username, Password: password} 70 | 71 | o := orm.NewOrm() 72 | if created, id, err := o.ReadOrCreate(user, "Username"); err == nil { 73 | if created { 74 | user.Id = int(id) 75 | } else { 76 | c.Data["ValidateMessage"] = "User existed." 77 | return 78 | } 79 | } else { 80 | c.Data["ValidateMessage"] = err 81 | return 82 | } 83 | ``` 84 | 85 | 这里用到了 `o.ReadOrCreate(user, "Username")` 来读取或者创建一个用户. 86 | 87 | 本段代码的 [commit][2] 88 | 89 | # 更新 godep 90 | 91 | 在上面的代码中, 新引入了第三方库 `github.com/mattn/go-sqlite3`, 它还不在 `Godeps` 里面, 要更新 `godep`, 运行`godep save`进行更新. 92 | 93 | 本段代码的 [commit][3] 94 | 95 | # 12 Factor 应用 96 | 97 | 上面的数据库我们用到了 `sqlite`, 在开发中, 它很方便, 快捷. 但是它不适合在生产环境使用, 最终在生产环境我们还是会使用其他数据库, 例如 `MySql`, `PostgreSql`. 98 | 虽然 `Beego` 的 `ORM` 已经做好了这三个兼容的适配器, 但是还是可能会有不同. 99 | 100 | 在 [12 Factor 应用](http://12factor.net/zh_cn/dev-prod-parity) 中提到, 要做到持续集成, 持续部署, 我们应该 尽量避免在不同的环境下使用不同的服务. 101 | 所以在这里, 我们会选择在开发中也使用与生产环境尽量一致的数据库和版本. 102 | 103 | ## 使用 MySql 104 | 105 | 因为使用了 `ORM`, 适配并隔离了各个数据库间的差异, 我们要使我们的代码兼容不同的数据库是很方便的: 106 | 107 | ```go 108 | import ( 109 | _ "github.com/go-sql-driver/mysql" 110 | ) 111 | func init() { 112 | orm.RegisterDataBase("default", "mysql", "db-user:db-pass@tcp(localhost:3306)/beefer?charset=utf8", 30) 113 | 114 | .... 115 | ) 116 | ``` 117 | 只需要更换相应的数据库驱动包, 并修改 `orm.RegisterDataBase` 注册就可以了. 我们还需要提前创建 `beefer` 数据库. 接着启动 `beefer` 应用, `beego` 变回自动帮你完成数据库同步了. 118 | 119 | 具体的参数可以参考 [github.com/go-sql-driver/mysql 的文档](https://github.com/go-sql-driver/mysql) 120 | 121 | ## 使用 PostgreSql 122 | 123 | 同样的, 对于喜欢 `PostgreSql` 的小伙伴, 也只需要简单的更改数据库适配器就可以了: 124 | 125 | ```go 126 | import ( 127 | _ "github.com/lib/pq" 128 | ) 129 | func init() { 130 | orm.RegisterDataBase("default", "postgres", "user=postgres password=mypass host=192.168.99.100 port=32770 dbname=beefer sslmode=disable", 30) 131 | 132 | .... 133 | ) 134 | ``` 135 | 136 | 同样, 在修改了适配器并创建好 `beefer` 数据库之后, 重启 `beefer` 应用, 数据库的同步变回自动完成. 137 | 138 | 具体的参数可以参考 [github.com/lib/pq 的文档](https://godoc.org/github.com/lib/pq) 139 | 140 | 本段代码的 [commit][4] 141 | 142 | [1]: 04.请求数据处理.md 143 | [2]: https://github.com/lei-cao/beefer/commit/13a144daada6dab2067ff8e283451dbd28b631f6 144 | [3]: https://github.com/lei-cao/beefer/commit/6539f6f12b16108afd240693cb337f5783086d6e 145 | [4]: https://github.com/lei-cao/beefer/commit/6b514ad9610f203c915914ea9bbfc4f0bf69b5c3 146 | 147 | -------------------------------------------------------------------------------- /zh/07.应用配置.md: -------------------------------------------------------------------------------- 1 | 7. 应用配置 2 | ========= 3 | 4 | 在上一章 [数据库和 ORM](06.数据库.md) 中, 我们需要使用到数据库的连接信息, 以及可能需要根据环境来配置是用不同的数据库. 5 | 这些信息有些是重要的敏感信息, 有些是根据环境的不同而不同的动态信息. 6 | 对于这些信息, 为了安全和方便, 我们要尽量避免将它们写死在项目中, 更要避免将这些信息提交到代码管理库中. 7 | 通常我们会根据环境的不同而生成这些配置信息, 通过配置文件或者环境变量的方式提供这些配置信息. 8 | 9 | # config 模块 10 | 11 | 为了解决这种需求, `beego` 提供了 [config 模块][1]. 12 | 13 | ```go 14 | import ( 15 | "github.com/astaxie/beego/config" 16 | ) 17 | 18 | const ( 19 | dbConfigPath string = "conf/db.conf" 20 | defaultDbName string = "beefer" 21 | ) 22 | 23 | dbConfig, err := config.NewConfig("ini", dbConfigPath) 24 | db := dbConfig.String("db") 25 | dbName := dbConfig.DefaultString("db_name", defaultDbName) 26 | ``` 27 | 28 | 首先引入 `github.com/astaxie/beego/config`, 然后通过 `config.NewConfig("ini", dbConfigPath)` 将 `ini` 文件 `dbConfPath` 中的配置信息读入到变量 `dbConfig` 中. 29 | 除了 `ini` 文件, `config 模块` 还支持 `json`、`xml`、`yaml` 格式. 30 | 31 | 接着我们便可以通过 `dbConfig.String("db")` 来读取配置文件中的配置项. 32 | 33 | 我们还可以使用 `dbConfig.DefaultString("db_name", defaultDbName)`, 如果没有该配置项, 则返回默认值 `defaultDbName`. 34 | 35 | `ini` 类型的配置文件还支持通过 `[section::key]` 的方式来获取一个段落下的配置, 例如通过 `dbUser := dbConfig.String("sqlite3::db_name")` 来获取: 36 | 37 | ```ini 38 | [sqlite3] 39 | db_name = ./db/beefer.db 40 | ``` 41 | 42 | 除了 `String()`, `config` 模式还支持其他方法, 比如 `Int()`, `Bool()`, `DIY()`, 具体请参见[文档][1]. 43 | 44 | ## 配置文件 45 | 46 | 上面的例子中设置了 `conf/db.conf` 来存放数据库配置信息. 47 | 同样, 我们还要避免将存有敏感信息的文件提交到代码库中, 因此我们在 `.gitignore` 中排除掉该文件, 并将保存有空的配置信息的模板 `conf/_db.conf`提交到代码库中. 48 | 在不同的环境中, 我们只需要拷贝 `conf/_db.conf` 为 `conf/db.conf` 并配置相应的信息就可以了. 49 | 50 | 本段代码的 [commit][2] 51 | 52 | # 应用结构 53 | 54 | 到目前为止, `beefer` 已经包含了 `应用配置 (config)`, `路由 (router)`, `控制器 (controller)`, `模型 (model)`, `视图 (view)` 这几部分. 55 | 但是除了 `视图`, 其他这几个模块还都混杂在同一个文件中, 对于只有很少页面和功能的微型应用, 这样最简单的结构也未尝不可. 但是对于一个正常的应用来说, 我们都应该对项目结构和目录进行分割和优化. 56 | 57 | 在现在的根目录下, 已经包含了各种类型的文件, 而且有许多都是和应用本身的逻辑无关的, 比如 `circle.yml`, `README.md`, `Dockerfile`, `db 目录`, 以及存放第三方依赖的 `Godeps` 目录, 随着项目开发的进行, 58 | 会有更多各种其他类型的文件被添加进来, 如果我们的项目的逻辑代码也直接放在根目录下, 整个项目就会变得非常杂乱无章. 所以我们最好增加一层目录来包含所有的项目代码. 一般我们可以把这层目录叫做 `app` 或者 `src`, 59 | 我个人更喜欢 `app`, 其中一个原因是按照字母排序它会出现在目录树的上方, 找起来方便, 也更体现了它的重要性, 对于阅读项目代码的人也更加一目了然. 当然你可以选择你喜欢的命名方式. 60 | 61 | ## 代码组织和命名规范 62 | 63 | 接着在 `app` 目录中, 我们可以创建 `controller.go` 文件, 并将 `beefer.go` 文件中的控制器相关的代码都移到里面. 64 | 但是我们已经有了 `BeeferController` 和 `UserController` 两个处理不同逻辑的控制器, 如果将他们都放在 `app/controller.go` 中, 结构还是不够清晰, 我们也无法方便的通过文件名来快速定位代码位置. 65 | 66 | 所以我们希望遵守一个 `controller` 文件只存放一个控制器和它相关的代码, 并给它起一个和控制器功能相关的名字, 比如 `feature.controller.go` 或者 `feature_controller.go`. 67 | 我们只要选择一种命名规则并在整个项目中遵循这个规则就可以了. 这里我们选择 `feature.controller.go` 这种命名方式. 68 | 69 | 同样的, 将 `User model` 相关的代码放到 `user.model.go`, 将 `路由` 放到 `beefer.router.go` 和 `user.router.go` 中. 70 | 71 | 最后, 我们创建了 `app.model.go` 来处理 `model` 公共的初始化的工作. 72 | 73 | 这样我们便有了如下的目录结构, 每个文件都只包含和文件名相关的逻辑, 每个文件的功能一目了然: 74 | 75 | ``` 76 | app/ 77 | app.model.go 78 | beefer.controller.go 79 | beefer.router.go 80 | user.controller.go 81 | user.model.go 82 | user.router.go 83 | ``` 84 | 85 | 本段代码的 [commit][3] 86 | 87 | 要继续对代码组织进行优化, 通常会有两条路: 88 | 89 | ### 按文件类型来组织: 90 | 91 | ``` 92 | app/ 93 | controllers/ 94 | beefer.go 95 | user.go 96 | models/ 97 | app.go 98 | user.go 99 | routers/ 100 | beefer.go 101 | user.go 102 | ``` 103 | 104 | 本段代码的 [commit][4] 105 | 106 | 也就是创建 `controllers`, `models`, `routers` 目录, 并将对应类型的文件分别放进去. 同时可以去掉文件名种的 `controller`, `model` 这些类型. 107 | 对于一般的中小型项目, 这种组织方式简单方便, 不需要处理复杂的引用和依赖关系. 但是它也有一些问题: 108 | 109 | 1. 命名冲突: 110 | 因为一个目录下会包含很多同类型的文件, 比如每个模块可能都有处理类似功能的 `Init` 函数, 我们就需要选择更特殊的名字. 111 | 112 | 2. 过度耦合的包, 但松散的功能模块: 113 | 按照类型组织的包会将同一个功能模块相关的代码分散在多个文件中, 当我们要修改一个模块的时候, 需要在多个目录下寻找. 114 | 而且因为在同一个包中我们可以直接引用变量和函数, 很有能一些代码就被放在与它本身功能无关的文件中, 虽然这不影响功能, 但最终会使得项目代码变得混乱. 115 | 当项目达到一定规模后, 这会增加更多的心智负担. 116 | 117 | 一开始 `beefer` 也会使用这种方式, 然后根据需要不断的去重构. 118 | 119 | ### 按功能模块来组织: 120 | 121 | 另外一种更清晰的方式便是按照功能模块来组织文件. 将完成同一功能的所有文件放在同一个目录中. 122 | 这种方式便解决了之前 [按文件类型来组织](#folders-by-type) 的问题, 使得整个项目结构更加的清晰. 但是也会有它的问题. 我们会在后面的章节继续讨论. 123 | 124 | ### 混合型 125 | 126 | 混合了 *按文件类型* 组织和 *按功能模块组织* 这两种方式, 在不同的情况下使用更合理的方式. 127 | 128 | [1]: http://beego.me/docs/module/config.md 129 | [2]: https://github.com/lei-cao/beefer/commit/7838f5cc742383fd0ddab5cd2eaa979a480e607c 130 | [3]: https://github.com/lei-cao/beefer/commit/f0a8ab1aae08927fd6a219b212eef1251da74ad8 131 | [4]: https://github.com/lei-cao/beefer/commit/c6a9b067c52b21199d7c2d5a19fc196c89187d74 132 | -------------------------------------------------------------------------------- /zh/05.CI-持续集成.md: -------------------------------------------------------------------------------- 1 | 5. 持续集成 Continuous Integration 2 | ========= 3 | 4 | 自动化测试在我们项目开发中起了十分重要的作用. 即便我们开发一个很小的项目, 完备的测试也可以提高我们的开发效率, 降低我们的维护成本, 提高我们应用的健壮性. 5 | 6 | 持续集成 (Continuous Integration) 则是建立在完备的自动化测试基础上的一种优秀的项目开发流程. 7 | 8 | 随着我们 `beefer` 应用开发的进行, 我们希望用户快速的看到我们的结果, 可以快速的试错, 快速的迭代. 这有助于我们尽早的沟通, 尽早的发现问题并解决问题. 9 | 10 | 我们希望我们每天完成的工作, 提交的代码都可以在第一时间被测试并得到测试报告, 如果测试成功, 我们希望新的代码可以第一时间被部署到目标服务器, 并呈现给用户. 如果测试失败, 我们希望第一时间修复问题, 再次快速进行测试流程. 11 | 12 | 这些流程, 我们需要自动化脚本和工具的支持, 对于持续集成工具, 我们有非常多的选择, 包括在线持续集成服务, 以及自行搭建的. 13 | 14 | 下面是一些常用服务或项目的列表: 15 | 16 | 1. [CircleCI](http://circleci.com) 是一个在线的 CI 系统. 使用起来也十分方便, 有对开源项目的免费版. 17 | 18 | 2. [Codeship](https://codeship.com/) 一个和 CircleCI 类似的在线 CI 系统, 也有针对开源项目的免费版. 19 | 20 | 3. [Drone](https://drone.io/) 是 `Go` 写的一个[开源的](https://github.com/drone/drone) CI 系统, 你可以自行搭建. 它同时还有一个[在线版本](https://drone.io/) 并对开源项目免费. 21 | 22 | 4. [Jenkins](http://jenkins-ci.org/) 是一个开源的自建 CI 系统. 对于希望自行定制复杂任务的用户, `Jenkins` 这个一个很好的选择. 许多大公司以及大型项目都在使用它. 23 | 24 | 5. [TeamCity](https://www.jetbrains.com/teamcity/) 是 JetBrains 出品的自建 CI 系统. 有免费的版本. 和 `Jenkins` 类似, `TeamCity` 支持自定义各种复杂的任务. 25 | 26 | 以上这里服务基本上满足了我们从开源小项目到企业级大项目的要求. 我们可以根据我们的需要来选择. 除此之外, 还有其他很多类似的 CI 系统, 可以参见 [这里](https://en.wikipedia.org/wiki/Comparison_of_continuous_integration_software) 27 | 28 | Github 最近 也新增了一个 [集成列表](https://github.com/integrations), 列出了更多的常用服务. 29 | 30 | 31 | # CircleCI 32 | 33 | 下面我们以 `CircleCI` 为例, 看看如何设置 CI 系统. 34 | 35 | [CircleCI](http://circleci.com) 的设置是很简单的. 在连接了我们的 `github` 库之后, 只需根据它的文档创建一个 `circle.yml` 文件: 36 | 37 | ```yml 38 | machine: 39 | services: 40 | - docker 41 | dependencies: 42 | pre: 43 | - mkdir -p ${GOPATH%%:*}/src/github.com/${CIRCLE_PROJECT_USERNAME} 44 | - rm -rf ${GOPATH%%:*}/src/github.com/${CIRCLE_PROJECT_USERNAME}/* 45 | - ln -sf ~/${CIRCLE_PROJECT_REPONAME} ${GOPATH%%:*}/src/github.com/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME} 46 | - go get github.com/tools/godep 47 | override: 48 | - echo "Nothing to install with Godep. Just build" 49 | - godep go build -v -race 50 | test: 51 | override: 52 | - godep go test -v -race ./... 53 | notify: 54 | webhooks: 55 | - url: https://hooks.slack.com/services/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 56 | ``` 57 | 58 | 本段代码的 [commit][1] 59 | 60 | 集成了 `CircleCI` 之后, 每当我们有新的提交 到 `github`, `CircleCI` 会自动的根据我们的配置对该提交进行测试, 并可以讲结果通过 IM 软件发送结果给我们, 我设置了 slack 来接收信息. 61 | 62 | 我们的[测试列表](https://circleci.com/gh/lei-cao/beefer) 63 | 64 | # 部署 65 | 66 | `beefer` 是随着本书写作的过程不断开发和完善的一个实例. 针对本书的每一小节的相关内容, 我们都部署了相应版本的 `beefer`, 使读者可以实时的看到对应代码的实例, 方便理解. 67 | 68 | 这就要求我们要能够快速的对 `beefer` 进行部署, 将结果快速展现给用户. 这里的道理在实际的项目开发中也是同样适用的. 只是读者变成了相应的用户. 要实现快速迭代和持续集成, 自动化部署是不可或缺的重要一环. 69 | 70 | ## Docker 71 | 72 | [Docker](https://www.docker.com/) 是现在很火的一个 `Go` 语言开发的容器平台. 它有很多的特性和优点, 可以大大的提高我们的开发和部署效率. `beefer` 同样使用了 `docker` 来完成 `构建`, `交付`, `运行` 的这个流程. 73 | 74 | `Docker` 可以通过创建相对独立的镜像 (image), 然后方便的在一台服务器上运行大量的容器 (container), 这一切都只要简单的几行命令就可以完成. 我们之前看到的实例就是在运行的不同的 `docker 容器`. 75 | 76 | [https://hub.docker.com/r/leicao/beefer/tags/](https://hub.docker.com/r/leicao/beefer/tags/) 是 `beefer` 镜像仓库的镜像列表. 它包含了我们的 `beefer` 在不同版本下的所有镜像. 每个镜像都只有 3M 大小, 交付起来是十分方便的. 77 | 78 | 如果你想快速的查看某个版本, 在安装了 `docker` 的前提下, 你只需要一行代码, 便可以实现: 79 | 80 | `docker run --rm -p 8085:8085 leicao/beefer:latest` 81 | 82 | 这时打开浏览器访问 `http://docker-host:8085/` 便可以看到最新版本的 `beefer`. 83 | 84 | 对于 `linux` 用户, `localhost` 便是你的 `docker-host`. 85 | 对于 `MacOS` 以及 `Windows` 的 `docker` 用户, 你的 `docker-host` 需要通过在命令行输入 `$ docker-machine ip default` 来获得. 具体请参见 `docker` 的官方文档. 86 | 87 | 下面来看看我们是如何创建这些镜像的: 88 | 89 | 下面是 `beefer` 相应的 `Dockerfile`: 90 | 91 | ```Dockerfile 92 | FROM scratch 93 | MAINTAINER Lei Cao "lei.cao.life@gmail.com" 94 | EXPOSE 8085 95 | WORKDIR /app 96 | # copy binary into image 97 | COPY beefer /app/ 98 | # copy the views into image 99 | COPY views /app/views/ 100 | # copy the static files into image 101 | COPY static /app/static/ 102 | ENTRYPOINT ["./beefer"] 103 | ``` 104 | 105 | 这里我们简单的将编译好的二进制文件以及模板和静态文件拷贝到镜像中, 并启动. 106 | 107 | 那么首先我们要先编译好 `beefer` 在 `linux` 系统下的可执行文件, 我们使用了 `docker` 的 [Golang 官方镜像](https://hub.docker.com/_/golang/) 来进行编译: 108 | 109 | `docker run --rm -v "$PWD":/usr/src/beefer -w /usr/src/beefer -e "GOPATH=/usr/src/beefer/Godeps/_workspace:/gopath" -e "CGO_ENABLED=0" -e "GOOS=linux" golang:1.5.1 go build -v -a -installsuffix cgo .` 110 | 111 | 通过上面的这行命令, 我们就编译出了可以在 `linux` 下独立运行的二进制文件 `beefer`. 112 | 113 | 接着我们就可以通过 `Dockerfile` 来创建我们的镜像了: 114 | 115 | `docker build -t leicao/beefer:latest .` 116 | 117 | 这样我们就轻松的创建了不同版本下的 `beefer` 镜像. 从不同的镜像运行容器, 便运行了不同版本的 `beefer` 应用. 十分方便. 118 | 119 | 这就使得我们一次构建, 处处运行. 无论你在本地, 还是测试服务器, 还是线上服务器, 无论你有多少台服务器, 你都可以快速的通过 `docker` 进行部署. 120 | 121 | [1]: https://github.com/lei-cao/beefer/commit/10259ed 122 | -------------------------------------------------------------------------------- /en/api-authentication.md: -------------------------------------------------------------------------------- 1 | The API Authentication 2 | ========= 3 | 4 | Providing API authentication is a challenge work. 5 | 6 | The HTTP protocol is *stateless* which means each HTTP request to the server is independent transaction and unrelated to any previous request. 7 | 8 | But in many cases we want to keep the session in order to communicate between the client and the server in a set of requests. So what's the solution? 9 | 10 | #Cookie-Based Authentication 11 | 12 | The traditional way is using **Cookie-Based Authentication**. The server creates the authenticated session by doing two things. First, the server creates and stores a session object on the server's session store, such as memory, database or file. The session is like a secure box and has its own identifier session key which can open the box and retrieve the information back from the secure box. Secondly, the server sends the session key to the client and creates a cookie to keep the session key. On the following requests the web browser attaches the cookie automatically and then the server can get the information from the secure box by the session key cookie. 13 | 14 | Because this approach is relied on the automatically sent cookie by web browser, there are some problems: 15 | 16 | - **Stateless and Server side scalability** 17 | 18 | The [RESTful API should be stateless](http://en.wikipedia.org/wiki/Representational_state_transfer#Stateless). There is no login or logout and no need to keep a session store on the API server to store the client state. The RESTful API server can scale easily. 19 | 20 | [See more here](http://stackoverflow.com/questions/3105296/if-rest-applications-are-supposed-to-be-stateless-how-do-you-manage-sessions) 21 | 22 | 23 | - **CSRF/XSRF** [Cross-site request forgery](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 24 | 25 | Since the traditional way is sending the session key by cookie, we need to deal with CSRF 26 | 27 | 28 | - **Mobile** 29 | 30 | If we want to make our API work on not only web browser but also other native applications, cookie is not a good choice for native applications. 31 | 32 | 33 | - **CORS** 34 | 35 | Let's say your front-end application is hosted on http://app.example.com and the API server is hosted on http://api.example.com 36 | 37 | Because of the *limit third party cookie policy*, by most of the browser's default settings, the Javascript hosted on app.example.com can't access the cookies set for api.example.com. 38 | 39 | 40 | #Token-Based Authentication 41 | 42 | To solve these issues of Cookie-Based Authentication we have a better solution which is Token-Based Authentication. 43 | 44 | Here are some great articles about Token-Based Authentication, you definitely should check them out. 45 | 46 | - [Cookies vs Tokens. Getting auth right with Angular.JS](https://auth0.com/blog/2014/01/07/angularjs-authentication-with-cookies-vs-token/) 47 | 48 | - [10 Things You Should Know about Tokens](https://auth0.com/blog/2014/01/27/ten-things-you-should-know-about-tokens-and-cookies/#token-size) 49 | 50 | 51 | ## JSON Web Token 52 | 53 | > JSON Web Token (JWT) is a compact URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is digitally signed using JSON Web Signature (JWS). IETF 54 | 55 | 56 | The JWT token contains all the information for keeping the session between the client and server, such as user id. There is no session data stored on the server so it's stateless. 57 | 58 | The JWT token is not sent alone with the cookie, so you don't need to worry about CSRF and CORS. 59 | 60 | The JWT authentication works good with RESTful API, doesn't rely on cookie, it works good with mobile app. 61 | 62 | ### The JWT Specification 63 | 64 | 65 | Here's an example: 66 | 67 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEyMzQ1Njc4OTAsIm5hbWUiOiJKb2huIERvZSIsImFkbWluIjp0cnVlfQ.eoaDVGTClRdfxUZXiPs3f8FmJDkDE_VCQFXqKxpLsts 68 | 69 | The JWT token is divided into 3 parts by `.`: 70 | 71 | 1. A header 72 | 2. A payload 73 | 3. A signature 74 | 75 | You can decode it at [jwt.io](http://jwt.io). 76 | 77 | #### 1. The Header 78 | 79 | The header `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9` after BASE64 decoding: 80 | 81 | ```json 82 | { 83 | "alg": "HS256", 84 | "typ": "JWT" 85 | } 86 | ``` 87 | 88 | `typ` is always "JWT" and the `alg` is the hash algorithm used to sign the token. Usually it's HMAC SHA-256. 89 | 90 | 91 | #### 2. The Payload 92 | 93 | The payload `eyJzdWIiOjEyMzQ1Njc4OTAsIm5hbWUiOiJKb2huIERvZSIsImFkbWluIjp0cnVlfQ` after BASE64 decoding: 94 | 95 | ```json 96 | { 97 | "sub":1234567890, 98 | "name":"John Doe", 99 | "admin":true 100 | } 101 | ``` 102 | 103 | You can store any information you want for keeping the session between the client and the server. For the APIs need the authorization, you need to send the token in the Authorization header, so watch out the size. 104 | 105 | #### 3. The Signature 106 | 107 | The signature `eoaDVGTClRdfxUZXiPs3f8FmJDkDE_VCQFXqKxpLsts` is signed based on the Payload, the Header, the secret key, and the `alg` in header. 108 | 109 | >You can read more about the Json Web Token [specification](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html). 110 | 111 | -------------------------------------------------------------------------------- /en/using-gulp.md: -------------------------------------------------------------------------------- 1 | Using Gulp 2 | ========= 3 | 4 | # What is Gulp 5 | 6 | > The streaming build system [Gulp](http://gulpjs.com/) 7 | 8 | Before we go deep into the Gulp let's think about the front-end application development life cycle. 9 | 10 | 1. We create the application structure like this: 11 | ``` 12 | app - 13 | |- styles 14 | |- scripts 15 | |- fonts 16 | |- images 17 | |- index.html 18 | ``` 19 | 20 | 2. We add the styles sheets, images and scripts and index.html we need and we link them together. 21 | We make the changes and we refresh the browser. We repeat this again and again. Very simple, right. 22 | 23 | 24 | However for the real application development what are we missing here? 25 | 26 | 1. We need to manage a lot of Javascript and CSS dependences. 27 | 28 | 2. We might want to use SASS, LESS, Jade, Coffeescript for development. So we need to compile them afterwards. 29 | 30 | 3. We need a local server to serve our application for many reasons such as accessing it from mobile devices. 31 | 32 | Before releasing our app, we need to optimize and version our static resources: 33 | 34 | 4. We might want to concatenate and minify our CSS files for better performance. 35 | 36 | 5. We might want to obfuscate and minify the javascript code by tools like Google Closure Compiler or UglifyJS. 37 | 38 | 6. We might want to optimize our images for better performance. 39 | 40 | 7. Most likely we will put our static files on the CDN for better performance and CDN will cache the files. 41 | Even the browser might cache the static files. So we need to version our static files to preventing the cache. 42 | 43 | Besides we might have other requirements such as: 44 | 45 | 8. We might need to change our code to different versions based on the environment such as different API endpoints for 46 | local development and production server. So we might need to pre-process our code before we release it. 47 | 48 | 9. We might want to run testcases of our application. 49 | 50 | 10. We might want to check out code quality by code quality tools like [JSHint](http://jshint.com) or [JSLint](http://www.jslint.com) 51 | 52 | All these repetitive tasks will drive us crazy if we try to to them manually. Shouldn't our time be better spent on more useful jobs? 53 | 54 | The benefits of task runners or build process is obvious now. 55 | 56 | There are several tools available out there: Grunt, Gulp, Broccoli 57 | 58 | Personally I prefer to using gulp. 59 | 60 | You can start using Gulp by folowwing it's [document](https://github.com/gulpjs/gulp/tree/master/docs) 61 | 62 | Generally Gulp has only 5 functions you need to learn: 63 | 64 | 1. `gulp.task(name, fn)` 65 | 66 | It registers the task function with a name. 67 | 68 | You can optionally specify some dependencies if other tasks need to run first. 69 | 70 | 2. `gulp.run(tasks...)` 71 | 72 | Runs all tasks with maximum concurrency 73 | 74 | 3. `gulp.watch(glob, fn)` 75 | 76 | Runs a function when a file that matches the glob changes 77 | 78 | 4. `gulp.src(glob)` 79 | 80 | This returns a readable stream. 81 | 82 | Takes a file system glob and starts emitting files that match. 83 | 84 | This is piped to other streams 85 | 86 | 5. `gulp.dest(folder)` 87 | 88 | This returns a writable stream 89 | 90 | File objects piped to this are saved to the file system 91 | 92 | The useful gulp plugins or npm libs you may need: 93 | 94 | 1. [gulp-minify-css](https://www.npmjs.org/package/gulp-minify-css) 95 | . [gulp-csso](https://www.npmjs.org/package/gulp-csso) 96 | . [gulp-preprocess](https://www.npmjs.org/package/gulp-preprocess) 97 | . [gulp-template](https://www.npmjs.org/package/gulp-template) 98 | . [connect](https://www.npmjs.org/package/connect) 99 | . [connect-livereload](https://www.npmjs.org/package/connect-livereload) 100 | . [gulp-livereload](https://www.npmjs.org/package/gulp-livereload) 101 | . [gulp-autoprefixer](https://www.npmjs.org/package/gulp-autoprefixer) 102 | . [gulp-bower-files](https://www.npmjs.org/package/gulp-bower-files) 103 | . [gulp-cache](https://www.npmjs.org/package/gulp-cache) 104 | . [gulp-clean](https://www.npmjs.org/package/gulp-clean) 105 | . [gulp-filter](https://www.npmjs.org/package/gulp-filter) 106 | . [gulp-flatten](https://www.npmjs.org/package/gulp-flatten) 107 | . [gulp-imagemin](https://www.npmjs.org/package/gulp-imagemin) 108 | . [gulp-jshint](https://www.npmjs.org/package/gulp-jshint) 109 | . [gulp-load-plugins](https://www.npmjs.org/package/gulp-load-plugins) 110 | . [gulp-ruby-sass](https://www.npmjs.org/package/gulp-ruby-sass) 111 | . [gulp-size](https://www.npmjs.org/package/gulp-size) 112 | . [gulp-uglify](https://www.npmjs.org/package/gulp-uglify) 113 | . [gulp-useref](https://www.npmjs.org/package/gulp-useref) 114 | . [gulp-inline](https://www.npmjs.org/package/gulp-inline) 115 | . [jshint-stylish](https://www.npmjs.org/package/jshint-stylish) 116 | . [opn](https://www.npmjs.org/package/opn) 117 | . [wiredep](https://www.npmjs.org/package/wiredep) 118 | 119 | There are many good resources about gulp out there: 120 | 121 | 1. [Building With Gulp](http://www.smashingmagazine.com/2014/06/11/building-with-gulp/) 122 | 123 | 2. [BrowserSync + Gulp.js](http://www.browsersync.io/docs/gulp/) 124 | 125 | 3. [Gulp - The streaming build system](http://slides.com/contra/gulp#/) 126 | 127 | 4. [Yeoman Web app generator with Gulp](https://github.com/yeoman/generator-gulp-webapp) 128 | 129 | 5. [My First Gulp Adventure](http://ponyfoo.com/articles/my-first-gulp-adventure) 130 | 131 | 6. And all the resources list on the [official document page](https://github.com/gulpjs/gulp/tree/master/docs) 132 | 133 | -------------------------------------------------------------------------------- /zh/04.请求数据处理.md: -------------------------------------------------------------------------------- 1 | 4. 请求数据处理 2 | ========= 3 | 4 | Web 客户端可以通过多种方式向服务端传递数据, 比如 `URL`, `header`, `body`, `cookie`. 我们来看在 `beego` 中如何使用他们. 5 | 6 | # URL 数据处理 7 | 8 | URL 中通常包含两类数据, URL `路径(path)`, 以及 URL `查询参数(query string)` 9 | 10 | 11 | ## 查询参数 (Query string) 12 | 13 | 在 `beego` 中获取查询参数是十分方便的, 使用 `beego.Controller.GetString()` 便可以方便的获取查询参数, (这个方法同样会获取 `request body` 中的以 POST 方式发送的表单参数): 14 | 15 | 16 | ``` 17 | // The Get method to handle the GET request 18 | func (c *BeeferController) Get() { 19 | name := c.GetString("user") 20 | user := User{Username: name} 21 | c.Data["User"] = user 22 | 23 | c.TplNames = "beefer.tpl" 24 | } 25 | ``` 26 | 27 | 本段代码的 [commit][1] 和 [在线演示] [1.1] 28 | 29 | 重新编译后访问[http://localhost:8085/?user=beego](http://localhost:8085/?user=beego), 你就会看到参数 `user` 的值已经传递到服务端并渲染到了客户端显示. 30 | 31 | 除了 `GetString()` 方法, `beego` 还提供了其他一些方便的方法来获取请求参数: 32 | ``` 33 | GetString(key string) string 34 | GetStrings(key string) []string 35 | GetInt(key string) (int64, error) 36 | GetBool(key string) (bool, error) 37 | GetFloat(key string) (float64, error) 38 | ``` 39 | 更多的获取参数的使用请参考文档: [获取参数](http://beego.me/docs/mvc/controller/params.md#%E8%8E%B7%E5%8F%96%E5%8F%82%E6%95%B0) 40 | 41 | ## 路径参数 (Path Parameters) 42 | 43 | 在上一章我们提到了路由, 并为 `beefer` 设置了基本的路由. 除了静态路由外, beego 还支持设置带参数的动态路由, 并通过 URL 路径来获取参数的值. 44 | 45 | 有时候我们为了得到结构更清晰, 用户和 SEO 都更友好的 URL 结构, 我们会希望使用 `path parameters`. 也就是用 URL 路径的一部分来作为我们获取参数值的另一种方式. 46 | 47 | 我们可以通过 `beego.Router("/user/:user", &BeeferController{})` 来设置路径参数, 并通过 `c.GetString(":user")` 来获取该参数. **注意这里的冒号 :user** 48 | 49 | ``` 50 | // The Get method to handle the GET request 51 | func (c *BeeferController) Get() { 52 | name := c.GetString(":user") 53 | user := User{Username: name} 54 | c.Data["User"] = user 55 | 56 | c.TplNames = "beefer.tpl" 57 | } 58 | 59 | func main() { 60 | beego.Router("/user/:user", &BeeferController{}) 61 | } 62 | ``` 63 | 64 | 本段代码的 [commit][2] 和 [在线演示] [2.2] 65 | 66 | 重新编译后访问[http://localhost:8085/user/beego](http://localhost:8085/user/beego), 你就会看到 `beego` 通过参数 `:user` 传递到了服务端. 67 | 68 | 关于更详细的路由设置, 请参考: 69 | [路由设置](http://beego.me/docs/mvc/controller/router.md#%E8%B7%AF%E7%94%B1%E8%AE%BE%E7%BD%AE) 70 | 71 | # Web 表单 (Web form) 72 | 73 | Web 表单是用户向服务端提交信息的主要方式. 表单可以通过 `GET` 方法通过 URL 的`查询参数`传递. 也可以通过 `POST` 方法通过`请求体 (request body)` 传递. 通常我们会选择 `POST` 方法. 74 | 75 | 下面让我们来看看如何使用 `beego` 获取表单数据. 76 | 77 | 下面是去除样式后最简化的注册模板文件 `user/signup.tpl`: 78 | 79 | ```html 80 | {{template "base/base.tpl" .}} 81 | {{define "main"}} 82 | {{if .ValidateMessage}} 83 | {{ .ValidateMessage }} 84 | {{end}} 85 |
86 | 87 | 88 | 89 | 90 | 91 |
92 | 93 | {{end}} 94 | ``` 95 | 96 | 接着我们修改了 `/user/signup` 路由, 让 `Signup` 方法可以同时处理 `get` 和 `post` 请求: 97 | `beego.Router("/user/signup", &UserController{}, "get:Signup;post:Signup")` 98 | 99 | 然后在 `Signup` 方法中加入下面的逻辑: 100 | 101 | ```go 102 | func (c *UserController) Signup() { 103 | c.TplNames = "user/signup.tpl" 104 | if c.Ctx.Request.Method == "POST" { 105 | username := c.GetString("username") 106 | password := c.GetString("password") 107 | password2 := c.GetString("password2") 108 | if password != password2 { 109 | c.Data["ValidateMessage"] = "两次密码不一致" 110 | return 111 | } 112 | user := User{Username: username} 113 | c.Data["User"] = user 114 | c.Redirect("/", 302) 115 | } 116 | } 117 | ``` 118 | 119 | 你会发现我们使用了和之前获取 URL 请求参数一样的方法 `GetString` 来获取表单 `POST` 的数据: `username := c.GetString("username")`. 120 | 121 | 这有别于其它一些语言例如 `PHP` 中的 `$_GET` 和 `$_POST`. 122 | 123 | 最后, 如果用户注册成功, 我们会将用户重定向到 `beefer` 首页. `c.Redirect("/", 302)` 124 | 125 | 本段代码的 [commit][3] 和 [在线演示] [3.3] 126 | 127 | 细心的读者会发现, 虽然我们在注册成功后, 在重定向前创建了用户 `user := User{Username: username}`, 并给 `controller` 设置了数据 `c.Data["User"] = user`, 但是在从定向后, 我们注册的用户名并没有显示在首页. 128 | 129 | 这是因为 `http` 是无状态的, 在我们重定向之后, 浏览器实际上会再次访问 "/", 此时会执行相应的 `Get` 方法, 我们注册的用户信息也就随之丢失了. 130 | 131 | 所以接下来, 我们需要在多个的请求之间共享和传递数据. 132 | 133 | 134 | # cookie 135 | 136 | 我们刚刚提到, 我们希望在多个请求间共享和传递数据, 其中一个常用选择是使用 `cookie`. 137 | 138 | `cookie` 是可以跨多个请求并长期保存在用户浏览器中的数据. 每当用户发起请求时, 浏览器会自动的在请求中发送 `cookie`. 下面我们来看如何在 `beego` 中使用 `cookie` 139 | 140 | ## 创建 cookie 141 | 我们通过 `c.Ctx.SetCookie(usernameCookieKey, username)` 来设置 `cookie`. 142 | 143 | ## 读取 cookie 144 | 我们添加了 `Prepare()` 方法来处理整个 `BeeferController` 公共的用户验证逻辑, 并通过 `username := c.Ctx.GetCookie(usernameCookieKey)` 从请求中读取 `cookie`. 145 | 146 | ```go 147 | // The Prepare() controller method runs before the real action methods 148 | func (c *BeeferController) Prepare() { 149 | username := c.Ctx.GetCookie(usernameCookieKey) 150 | if username == "" { 151 | return 152 | } 153 | user := User{Username: username} 154 | c.CurrentUser = &user 155 | c.Data["CurrentUser"] = c.CurrentUser 156 | } 157 | ``` 158 | 159 | ## 删除 cookie 160 | 在删除 `cookie` 的时候, 我们可以设置过期时间为过去的一个时间, 但是浏览器并不一定会如我们所愿删除这个 `cookie` 所以最好还要给 `cookie` 设置空值: `c.Ctx.SetCookie(usernameCookieKey, "")` 161 | 162 | 本段代码的 [commit][4] 和 [在线演示] [4.4] 163 | 164 | 这时候聪明的你会发现, 我们通过给 `cookie` 设置值的方式, 虽然做到了在多个请求间共享和传递数据, 但是它十分的不安全, 因为用户可以随意的修改 `cookie` 值来伪造身份. 165 | 166 | 下一节我们来看看如何安全的使用 `cookie` 167 | 168 | 169 | 170 | # Session 171 | 在上一节中, 我们通过设置 `cookie` 来记录用户的信息和状态, 但显然对于敏感信息这是不安全的, 我们还需要对 `cookie` 数据进行加密. `beego` 提供了 [session](http://beego.me/docs/mvc/controller/session.md) 模块来帮助我们处理跨请求的数据. 172 | 173 | `session` 模块支持的后端引擎包括 `memory`, `cookie`, `file`, `mysql`, `redis`, `couchbase`, `memcache`, `postgres`, 基本满足了我们大多数情况下的需求. 当然你可以自己添加其他引擎. 174 | 175 | 我们需要在 `beego.Run()` 之前启用 `session`: `beego.SessionOn = true`, 并使用默认的 `memory` 引擎. 176 | 177 | 接着我们使用 `SetSession`, `GetSession`, `DelSession` 来替换之前的 `cookie` 操作. 刷新页面, 并查看浏览器的 `cookie` 你会看到一个名为 `beegosessionID` 的 `cookie`. `beego` 通过这个 `sessionId` 来操纵存在 `session` 引擎中的 `session` 对象. 178 | 179 | 要注意的是, 在开发中我们使用了 `memory` 作为引擎, `session` 信息是存在内存中的, 当我们重启应用后, 虽然 `cookie` 中仍然保存着 `sessionId`, 但是在内存中已经找不到相关的 `session` 信息了. 所以在生产环境, 为了更好的用户体验, 我们应当选择其他的引擎. 180 | 181 | 同时要注意, 因为 `beego` 的 `session` 使用了 [gob](https://golang.org/pkg/encoding/gob/) 来存储对象, 所以在使用相关对象之前, 要先进行 `gob` 注册. 通常我们会将 `gob` 注册写在 `init()` 函数中, 以保证这些对象在使用前已经被注册了: 182 | 183 | `gob.Register(&User{})` 184 | 185 | 本段代码的 [commit][5] 和 [在线演示] [5.5] 186 | 187 | [1]: https://github.com/lei-cao/beefer/commit/bba3757 188 | [1.1]: http://s04-1.lei-cao.com?user=beego 189 | [2]: https://github.com/lei-cao/beefer/commit/899422e 190 | [2.2]: http://s04-2.lei-cao.com/user/beego 191 | [3]: https://github.com/lei-cao/beefer/commit/2456e19 192 | [3.3]: http://s04-3.lei-cao.com/user/signup 193 | [4]: https://github.com/lei-cao/beefer/commit/5e7ee9b 194 | [4.4]: http://s04-4.lei-cao.com/user/signup 195 | [5]: https://github.com/lei-cao/beefer/commit/663dfaa 196 | [5.5]: http://s04-5.lei-cao.com/user/signup 197 | -------------------------------------------------------------------------------- /zh/03.模板.md: -------------------------------------------------------------------------------- 1 | 3. 模板 2 | ========= 3 | 4 | 在上一章中我们的 `beefer` 返回了 "Hello Beego" 的字符串作为 Http 的响应. 而在许多 Http 应用中, 5 | 我们需要用到模板. 6 | 7 | # 为何我们需要模板 8 | 9 | 通常我们的 web 应用会包含不会随着请求的变化而变化的静态的内容, 以及可能随请求的不同而变化的动态的内容. 10 | 我们希望能够重用这些静态内容, 然后只改变那些动态的内容, 这时我们就需要用到模板. 11 | 12 | 我们接着来扩展 `beefer`, 给它加一个欢迎信息给不同的访问用户. 修改 `beefer.go` 的 Get() 方法: 13 | 14 | ```go 15 | type User struct { 16 | Username string 17 | } 18 | 19 | // The Get method to handle the GET request 20 | func (c *BeeferController) Get() { 21 | var tpl string = ` 22 | 23 | 24 | Beefer! 25 | 26 | 27 | Hello, {{.User.Username}} 28 | 29 | 30 | ` 31 | data := make(map[interface{}]interface{}) 32 | user := User{Username: "Alice"} 33 | data["User"] = user 34 | 35 | t := template.New("Beefer Template") 36 | t = template.Must(t.Parse(tpl)) 37 | 38 | t.Execute(c.Ctx.ResponseWriter, data) 39 | } 40 | ``` 41 | 42 | 本段代码的 [commit][1] 和 [在线演示] [1.1] 43 | 44 | 这里我们暂时新建了模拟对象 `User`. 然后定义了 `tpl` 模板, 里面 `{{.User.Username}}` 会根据用户的不同动态的输出数据. 45 | 接着我们通过模板的 `Execute` 函数用新建的 `user` 替换模板中的动态内容并输出到 `c.Ctx.ResponseWriter`. 46 | 如果你已经通过 `bee run` 启动了你的 `beefer`, 这时访问 [http://localhost:8085](http://localhost:8085) 47 | 你就会看到输出的 html 响应: "Hello, Alice" 48 | 49 | # MVC 架构: 模板和控制器的分离 50 | 51 | 现在我们的模板文件和控制器逻辑还混在一起, 这样的代码结构在我们的应有有更多功能时, 会比较难维护. 52 | 我们希望能够将用来展示内容的模板和用来处理逻辑的控制器分离开, 从而使我们的代码有更好的组织结构. 53 | 我们可以将 `beefer` 改成这样的结构: 54 | 55 | ``` 56 | ├── beefer.go 57 | └── views 58 | └── beefer.tpl 59 | ``` 60 | 61 | 我们将之前在 `tpl` string 变量里的内容移到 `views/beefer.tpl` 文件中. 并修改 `Get()` 方法如下: 62 | 63 | ```go 64 | func (c *BeeferController) Get() { 65 | user := User{Username: "Alice"} 66 | c.Data["User"] = user 67 | 68 | c.TplNames = "beefer.tpl" 69 | } 70 | ``` 71 | 72 | 本段代码的 [commit][2] 和 [在线演示] [2.2] 73 | 74 | 75 | 重新刷新浏览器, 我们会得到相同的结果: "Hello, Alice". 76 | 77 | 78 | 79 | # 样式 80 | 81 | 在继续为 `beefer` 添加更多的内容之前, 如果我们可以很方便的美化 `beefer` 的话, 那么我们接下来的开发也会变得更加赏心悦目. 82 | 83 | 要快速为我们的 `beefer` 应用基本的样式, 我们可以使用一些 CSS 框架, 比如 [Bootstrap](http://getbootstrap.com), [Foundation](http://foundation.zurb.com). 84 | 85 | 这里我选择了基于 `Bootstrap` 的样式 [Bootswatch - Paper](http://bootswatch.com/paper/). 86 | 87 | 首先在 `beefer/` 目录下新建如下的目录结构, 并下载 `bootstrap.css` 文件: 88 | 89 | ``` 90 | └── static 91 | ├── css 92 | │ └── bootstrap.css 93 | ├── js 94 | └── images 95 | ``` 96 | 97 | 接着我们需要在 `views/beefer.tpl` 中引入 `bootstrap`, `bootstrap` 要工作还需要 `jquery`, 我们一起引入: 98 | 99 | ```html 100 | 101 | 102 | Beefer! 103 | 104 | 105 | 106 | Hello, {{.User.Username}} 107 | 108 | 109 | 110 | 111 | ``` 112 | 本段代码的 [commit][3] 和 [在线演示] [3.3] 113 | 114 | # 模板中的条件控制 115 | 116 | 在引入了 `bootstrap` 的样式后, 我们可以继续为 `beefer` 添加内容, 首先我们来添加一个简单的导航栏. 117 | `bootstrap` 的具体使用超出了本书的范围, 在这里我们不做过多的介绍. 小伙伴可以自行学习或者选择熟悉的 css 框架. 118 | 119 | ```html 120 | 146 | ``` 147 | 148 | 在这个导航栏中, 我们用到了 `go` 模板中的条件判断 `if ... else ... end`, 如果用户存在, 则输出欢迎信息和 **LOG OUT** 按钮. 149 | 如果用户不存在, 则输出 **LOG IN** 和 **SIGN UP** 按钮. 150 | 151 | 同时在我们的 `beefer.go` 应用中, 我们增加了相关的控制器来处理用户相关的请求, 代码片段如下, 完整的代码可以查看 commit: 152 | 153 | ```go 154 | // ....... 155 | // The UserController to handle user related actions. 156 | type UserController struct { 157 | beego.Controller 158 | } 159 | 160 | // Login method 161 | func (c *UserController) Login() { 162 | c.TplNames = "beefer.tpl" 163 | } 164 | 165 | // Signup method 166 | func (c *UserController) Signup() { 167 | c.TplNames = "beefer.tpl" 168 | } 169 | 170 | // Logout method 171 | func (c *UserController) Logout() { 172 | c.TplNames = "beefer.tpl" 173 | } 174 | // ....... 175 | 176 | 177 | func main() { 178 | // ....... 179 | beego.Router("/user/login", &UserController{}, "get:Login") 180 | beego.Router("/user/signup", &UserController{}, "get:Signup") 181 | beego.Router("/user/logout", &UserController{}, "post:Logout") 182 | // ....... 183 | } 184 | ``` 185 | 本段代码的 [commit][4] 和 [在线演示] [4.4] 186 | 187 | 这里我们注册路由时用到了 `beego.Router("/user/login", &UserController{}, "get:Login")`, 比起之前多出了 `"get:Login"` 参数, 188 | 这个参数将会告诉 `beefer`, 对发到 `/user/login` 的 **GET** HTTP 请求, 交给 `UserController{}` 中的 `Login` 方法来处理. 同样类似的, 189 | 对于发到 `/user/logout` 的 **POST** HTTP 请求, 交给 `Logout` 方法来处理. 190 | 191 | # 子模板 192 | 193 | 为了方便控制以及减少重复, 我们希望将模板中的共同部分抽取出来作为父模板, 不同的部分作为子模板来根据页面的不同来改变. 194 | 195 | 下面是我们最基础的模板 `base/base.tpl`: 196 | 197 | ```html 198 | 199 | 200 | Beefer! 201 | 202 | 203 | 204 | {{template "base/navbar.tpl" .}} 205 | {{template "main" .}} 206 | 207 | 208 | 209 | 210 | ``` 211 | 212 | 我们分别将导航栏和 `body` 的主要内容抽取到了两个 `template`: `{{template "base/navbar.tpl" .}}` 和 `{{template "main" .}}` 中. 213 | 214 | 接着我们便可以创建基于这个基础模板的子模板 `user/login.tpl`: 215 | 216 | ```html 217 | {{template "base/base.tpl" .}} 218 | {{define "main"}} 219 | Login! 220 | {{end}} 221 | ``` 222 | 223 | 本段代码的 [commit][5] 和 [在线演示] [5.5] 224 | 225 | 226 | `beego` 的默认模板引擎就是 `Go` 原生的 `template` 包. 关于 `template` 更多的使用可以参考: 227 | 228 | [模板处理](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/07.4.md) 229 | 230 | [beego 模板语法指南](https://github.com/Unknwon/gcblog/blob/master/content/03-beego-template.md) 231 | 232 | 233 | 234 | 下一章我们将给 `beefer` 传递数据. 235 | 236 | 237 | [1]: https://github.com/lei-cao/beefer/commit/aace6c9 238 | [1.1]: http://s03-1.lei-cao.com 239 | [2]: https://github.com/lei-cao/beefer/commit/519dfdf 240 | [2.2]: http://s03-2.lei-cao.com 241 | [3]: https://github.com/lei-cao/beefer/commit/98f2b18 242 | [3.3]: http://s03-3.lei-cao.com 243 | [4]: https://github.com/lei-cao/beefer/commit/c663eb8 244 | [4.4]: http://s03-4.lei-cao.com 245 | [5]: https://github.com/lei-cao/beefer/commit/d3c583d 246 | [5.5]: http://s03-5.lei-cao.com 247 | -------------------------------------------------------------------------------- /zh/beego-in-docker.md: -------------------------------------------------------------------------------- 1 | 装在 Docker 里面的 Beego 2 | ================ 3 | 4 | [**English Version**](../en-US/beego-in-docker.md) 5 | 6 | [Docker](http://docker.io) 是一个快速创建一致的开发和部署环境的工具. 每一个程序员已经习惯了去一遍又一遍的安装和配置开发环境. 我们已经习惯了各种各样的系统库不兼容的 bug. 每次升级系统, 我们的开发环境都可能轻易的崩溃. 我们还经常遇到开发环境和生产环境不一致导致的奇怪的 bug. 幸运的是我们现在有了 Docker. 小伙伴们要是想愉快的开发, Docker 是你必须要尝试的工具. 7 | 8 | Docker 最近很火, 已经有很多文章介绍 Docker, 小伙伴们可以自己去看. 这里我会介绍如何结合 Docker 来进行 [Beego](http://beego.me) 的开发. 9 | 10 | - [Docker homepage](https://www.docker.com/) 11 | - [Docker: Lightweight Linux Containers for Consistent Development and Deployment](http://www.linuxjournal.com/content/docker-lightweight-linux-containers-consistent-development-and-deployment) 12 | - [Docker: Using Linux Containers to Support Portable Application Deployment](http://www.infoq.com/articles/docker-containers) 13 | - [Deploying Go servers with Docker](https://blog.golang.org/docker) 14 | - [利用Docker构建开发环境](http://tech.uc.cn/?p=2726) 15 | - [深入浅出Docker](http://www.infoq.com/cn/dockers) 16 | 17 | 在对 Docker 有了基本的了解之后, 让我们开始用 Docker 进行 Beego 开发吧. 我们[最后的程序](http://book.beego.me/)可以在这里看到. 下面是这个部分的目录: 18 | 19 | 1. [创建服务器](#setup) 20 | 21 | 2. [安装 Docker](#install) 22 | 23 | 3. [使用 Golang 的 Docker Image](#golang-docker-image) 24 | 25 | * [不用安装的 Golang env](#golang-env) 26 | 27 | * [hello world](#hello-world) 28 | 29 | 4. [创建 Beego 的 Docker Image](#beego-docker-image) 30 | 31 | 5. [使用 Mysql Docker image](#mysql-docker-image) 32 | 33 | 6. [使用 Nginx Docker image](#nginx-docker-image) 34 | 35 | 7. [联合使用这些 Docker](#wire-up) 36 | 37 | 8. [总结](#conclusion) 38 | 39 | 40 | 41 | 1. 创建服务器 42 | ------------- 43 | 44 | 这个例子是在线上的服务器. 当然小伙伴们也可以在自己的本机来玩. 没有本质的区别. 45 | 46 | 我的服务器是在 [Linode](https://www.linode.com/) 买的, 安装了 Ubuntu 14.10. 然后讲 `book.beego.me` 指导了这个服务器. 47 | 48 | 49 | 2. 安装 Docker 50 | ------------- 51 | 52 | 首先你需要安装 Docker. 不同的系统安装方式可能会有不同. 你可以根据自己的系统, 参考官方文档来安装 [installation](https://docs.docker.com/installation/#installation). 53 | 54 | 对于我的 Ubuntu 服务器, 只需要这么三行命令: 55 | ```bash 56 | $ sudo apt-get update 57 | $ sudo apt-get install docker.io 58 | $ source /etc/bash_completion.d/docker.io 59 | ``` 60 | 61 | 3. 使用 Golang 的 Docker Image[Golang Docker image](https://registry.hub.docker.com/_/golang/). 62 | ------------- 63 | 64 | Docker 有一个 [Golang 官方库](https://registry.hub.docker.com/_/golang/). 你可以直接用它最为你的 Go 开发环境. 然后我们会从 Hello World 开始. 65 | 66 | * ### 不用安装的 Golang env 67 | 68 | 你只要运行下面的命令就可以从 [Golang 官方库](https://registry.hub.docker.com/_/golang/) 创建一个包括 Golang 开发环境的 Docker Container. 69 | 70 | `docker run -it golang /bin/bash` 71 | 72 | 这时, 你已经有了最新的 Golang 开发环境: 73 | 74 | `go version` 75 | 76 | 你会看到如下图的结果. 我通过在 Golang Image 上运行 `/bin/bash` 来创建了一个 Container. `-it` 选项会让 Docker 创建一个可交互的命令行界面. 之后在这个新建的 Container 内, 我输入 `go version` 来测试我的 Golang 环境. 77 | 78 | ![](../images/docker.golang.png?raw=true) 79 | 80 | 在你第一次运行 `docker run ` 的时候, 如果 Docker 没有在本机找到对应的 Image, 则会自动到 Docker 库中寻找. 81 | 82 | 通过下面的命令, 你可以查看本地存在的 Docker Image. 83 | 84 | `docker images` 85 | 86 | 通过下面的命令, 你可以查看你创建的 Container. 87 | 88 | `docker ps -a` 89 | 90 | 91 | * ### Hello World 92 | 93 | `docker run -it -v /home/leicao/programming/go/hello-world/:/go/src/hello-world golang /bin/bash` 94 | 95 | 从 Golang Image 创建了 Container 之后, 你就可以在这个环境上面编译 Go 程序了. 96 | 97 | `go build hello.go` 98 | `./hello` 99 | 100 | 你会看到类似如下的结果: 101 | 102 | ![](../images/docker.golang.hello-world.png?raw=true) 103 | 104 | 你可能会纠结这一切是怎么来得. 我来解释一下: 105 | 106 | 在上面的图片中: 107 | 108 | `/home/leicao/programming/go/hello-world/hello.go` 是一个存在我的 Ubuntu 主机里面的一个 Go 的 hello world 源码: 109 | 110 | ```go 111 | package main 112 | 113 | import "fmt" 114 | 115 | func main() { 116 | fmt.Println("Hello, 世界") 117 | } 118 | ``` 119 | 120 | 接着我们运行了下面的命令来创建我们的 Docker Container: 121 | 122 | `docker run -it -v /home/leicao/programming/go/hello-world/:/go/src/hello-world golang /bin/bash` 123 | 124 | 我们使用了选项 `-v /home/leicao/programming/go/hello-world/:/go/src/hello-world`, 它将我的 Ubuntu 主机上的目录 `/home/leicao/programming/go/hello-world/` 挂载到创建的 Docker Container 的 `/go/src/hello-world` 目录. 125 | 126 | 这样, 在我的 Docker Container 里面, 我便可以得到我的 hello world 源码了. 之后我 `cd src/hello-world/` 并编译它 `go build hello.go` 最后 `./hello` 来运行它. 127 | 128 | 是不是很爽? 有了 Docker, 我们就不用再自己安装和配置开发环境了, 一切都可以直接拿来用. 129 | 130 | 131 | 4. [创建 Beego 的 Docker Image](https://registry.hub.docker.com/u/leicao/beego/). 132 | ------------------- 133 | 134 | 我们现在有了最基本的 Golang 开发环境, 我们可以在它的基础上进行扩展, 从而支持 Beego 的开发. 我已经创建了一个最基本的 [Beego Image](https://registry.hub.docker.com/u/leicao/beego/), 你可以直接拿来用. 实际上, 你自己 build Docker image 也是非常的容易的. 135 | 136 | 首先创建一个 `Dockerfile`: 137 | 138 | ```dockerfile 139 | # Base image is in https://registry.hub.docker.com/_/golang/ 140 | # Refer to https://blog.golang.org/docker for usage 141 | FROM golang:1.3.3 142 | MAINTAINER Lei Cao lexo.charles@gmail.com 143 | 144 | # ENV GOPATH /go 145 | 146 | # Install beego & bee 147 | RUN go get github.com/astaxie/beego 148 | RUN go get github.com/beego/bee 149 | ``` 150 | 151 | `FROM golang:1.3.3` 这个 Docker image 从官方的 Golang Image 创建, 152 | 之后我们运行 `RUN go get github.com/astaxie/beego` 和 `RUN go get github.com/beego/bee` 来安装 Beego 和 Bee 的依赖. 153 | 154 | 那么我们怎么使用这个 Dockerfile 呢? 155 | 156 | - 第一种方式是你自己 build, 在这个 Dockerfile 所在目录下运行下面的命令 157 | 158 | `docker build -t leicao/beego .` 159 | 160 | 非常的简单, 接着你就可以在 `docker images` 里面看到它了 161 | 162 | - 第二种方式更好更方便, 那便是使用 Docker 官方的 [自动化 build](https://docs.docker.com/docker-hub/builds/#the-dockerfile-and-automated-builds) 163 | 164 | 根据这个教程, 为你的 Dockerfile 创建 git repo, 我的是 [github.com/lei-cao/dockers](https://github.com/lei-cao/dockers). 之后我就得到了自动 build 的 [beego image](https://registry.hub.docker.com/u/leicao/beego/). 165 | 166 | 有了 Beego 的 Docker image, 我们就可以和它开心的玩耍了. 167 | 168 | 我们可以使用 `bee` 工具来创建 beego 应用: 169 | 170 | `docker run --rm -v "$(pwd)":/go/src/ -w /go/src leicao/beego bee new beego-docker` 171 | 172 | 173 | `--rm` 告诉 Docker 自动删除这个 Container 当执行结束后. `-v "$(pwd)":/go/src/` 会挂载我的当前目录到 Docker Container 里面的 `/go/src` 目录. `-w /go/src` 指定运行命令的目录. `leicao/beego` 是这里所使用的 Docker image, 也就是我们刚刚创建的那个 Beego Image. 最后通过 `bee new beego-docker` 创建了一个 beego 应用 `beego-docker`. 174 | 175 | 这里最开心的部分就是目录的挂载, 使得我们可以用 Docker Container 来在主机上创建和修改文件, 完成开发, 而不用配置开发环境以及担心开发环境的依赖. 程序员的人生本来就该如此美好. 176 | 177 | 接着我们就可以运行我们的 `beego-docker` 项目了 178 | 179 | `docker run --rm -v "$(pwd)"/beego-docker:/go/src/beego-docker -w /go/src/beego-docker -p 8081:8080 leicao/beego bee run` 180 | 181 | 这里使用了一个新的选项 `-p 8081:8080`, 它将 Docker container 内部的 8080 端口映射到主机的 8081 端口. 这时, 我们的 beego 应用已经可以访问了: [http://book.beego.me:8081](http://book.beego.me:8081) 182 | 183 | ![](../images/docker.bee.run.png?raw=true) 184 | 185 | 186 | 5. 使用 [Mysql Docker image](https://registry.hub.docker.com/_/mysql). 187 | ------------------ 188 | 189 | 和我们刚才玩的 Golang Docker Image 类似, 我们可以通过一个命令运行起一个 Mysql server. 190 | 191 | `docker run --name db -e MYSQL_ROOT_PASSWORD=root -d mysql` 192 | 193 | 这个命令会从官方的 mysql Docker image 上运行一个名字叫做 `db` 的 container. 并通过选项 `-e MYSQL_ROOT_PASSWORD=root` 将 root 的密码设置成了 `root`. 194 | 195 | 要连接到这个 Mysql server: 196 | 197 | `docker run -it --link db:mysql mysql sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p'` 198 | 199 | 这里又用到了一个新的选项, `--link db:mysql`, 它通过 container 的 name `db` 在两个 container 直接创建了连接, 从而可以使用另外一个 container 的资源. 我们可以在 Mysql 命令行创建我们的数据库 `create database beego;` 200 | 201 | ![](../images/docker.mysql.png?raw=true) 202 | 203 | > [Docker 关于 links 的文档](http://docs.docker.com/userguide/dockerlinks/) 查看更多关于 links 的细节 204 | 205 | 6. 使用 [Nginx Docker image](https://registry.hub.docker.com/_/nginx). 206 | ------------------ 207 | 208 | And one more thing, Nginx. 209 | 210 | 先运行我们的 beego 应用: 211 | 212 | `docker run --rm --name beego -v "$(pwd)"/beego-docker:/go/src/beego-docker -w /go/src/beego-docker -p 8080:8080 leicao/beego bee run` 213 | 214 | 下面是 Nginx 的配置文件: 215 | 216 | ``` 217 | server { 218 | listen 80; 219 | server_name book.beego.me; 220 | 221 | client_max_body_size 20m; 222 | client_header_timeout 1200; 223 | client_body_timeout 1200; 224 | send_timeout 1200; 225 | keepalive_timeout 1200; 226 | 227 | location / { 228 | proxy_pass http://beego:8080; 229 | } 230 | } 231 | 232 | ``` 233 | 234 | 然后启动 Nginx: 235 | 236 | `docker run --name nginx --link beego:beego -v "$(pwd)"/nginx:/etc/nginx/conf.d/ -p 80:80 -d nginx` 237 | 238 | 你可能会疑惑为什么用 `proxy_pass http://beego:8080;`. 事实上当我们使用 `--link beego:beego` 的时候, Docker 会在 Nginx 的 container 里面的 `/etc/hosts` 文件中设置一个 host, 将 Beego container 的 ip 指向 它的名字 `beego`. 239 | 240 | ![](../images/docker.link.hosts.png?raw=true) 241 | 242 | 7. 联合使用这些 Docker 243 | ----------------- 244 | 245 | 我们现在有了 Golang, Beego/Bee, Mysql, Nginx. 接下来让我们 参照这个例子 [bee api application](http://beego.me/blog/beego_api) [Youtube](http://youtu.be/w7RziV_Sn-g), 创建一个 Beego Api 应用 246 | 247 | 我们来创建一个 beeblog api 应用. [这里是数据库的结构](medias/beeblog.sql) 248 | 249 | - 启动一个 Mysql 数据库服务, 并初始化数据库 beeblog: 250 | 251 | `docker run --name db -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=beeblog -d mysql` 252 | 253 | 254 | - 导入数据库结构: 255 | 256 | `docker run --rm --link db:mysql -v "$(pwd)"/mysql:/home/mysql -it mysql sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p beeblog < /home/mysql/beeblog.sql'` 257 | 258 | 259 | - 生成 Beego Api 应用: 260 | 261 | `docker run --rm --link db:mysql -v "$(pwd)":/go/src/ -w /go/src leicao/beego bee api beeblog -conn="root:root@tcp(mysql:3306)/beeblog"` 262 | 263 | 264 | - 运行 Api 应用: 265 | 266 | `docker run --rm --link db:mysql -v "$(pwd)/beeblog":/go/src/beeblog -w /go/src/beeblog -p 8080:8080 --name beego leicao/beego bee run -downdoc=true -gendoc=true` 267 | 268 | 或者编译运行: 269 | `sudo docker run --link db:mysql -v "$(pwd)/beeblog":/go/src/beeblog -w /go/src/beeblog -p 8080:8080 --name beego -d leicao/beego ./beeblog` 270 | 271 | 272 | - 启动 Nginx: 273 | 274 | `docker run --name nginx --link beego:beego -v "$(pwd)"/nginx:/etc/nginx/conf.d/ -p 80:80 -d nginx` 275 | 276 | 在这里便可以访问我们创建的 Api 应用了: 277 | 278 | API: [http://book.beego.me/v1/posts](http://book.beego.me/v1/posts) 279 | 280 | API 文档: [http://book.beego.me/swagger/swagger-1/](http://book.beego.me/swagger/swagger-1/) 281 | 282 | ![](../images/docker.swagger.png?raw=true) 283 | 284 | 8. [总结](#conclusion) 285 | ----------- 286 | 287 | 前面我们介绍了 Docker, Docker image, Docker container 的基本概念以及基本使用方法. 我们不再需要花费大量的时间去配置开发环境了. 一开始你可能会对这些长长的 Docker 命令感到陌生, 但是当你熟悉他们只会, 你就会发现其实他们是很容易使用的. 288 | 289 | 为了避免过多手动的进行 Docker 的操作, 你可以使用其他一些 Docker 工具来简化操作. [Fig](http://www.fig.sh) 便是其中之一. 后面我还会介绍 Fig 的使用. 290 | 291 | 292 | 293 | 294 | 295 | 296 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /en/beego-in-docker.md: -------------------------------------------------------------------------------- 1 | Beego in Docker 2 | ================ 3 | 4 | [**简体中文版**](../zh-CN/beego-in-docker.md) 5 | 6 | [Docker](http://docker.io) is a amazing tool to create the consistent environment for development and deployment. We developers have already gotten used to spend hours to set up the development environment to just start writing code. We've spent so many times in the dependency hell. Our development easily get crashed while we upgrade our system. We encounter wired bugs because the development environment is different from the production server environment. Thankfully we have Docker now. It's the must-try tool to make your development easier and happier. 7 | 8 | There are already many articles introduce you what is Docker, how to install Docker and get started with it. I will directly talk about how to set up the development environment for developing [beego](http://beego.me) applications. If you are new to Docker, it's better to check out the links below first. 9 | 10 | 11 | - [Docker homepage](https://www.docker.com/) 12 | - [Docker: Lightweight Linux Containers for Consistent Development and Deployment](http://www.linuxjournal.com/content/docker-lightweight-linux-containers-consistent-development-and-deployment) 13 | - [Docker: Using Linux Containers to Support Portable Application Deployment](http://www.infoq.com/articles/docker-containers) 14 | - [Deploying Go servers with Docker](https://blog.golang.org/docker) 15 | 16 | After you getting the basic concept of Docker, let's start to dockerize our production ready Beego application. Here is our [final application](http://book.beego.me/), nothing much, just the beego application default page with reading the data from the mysql database dynamically. Here is our outlines: 17 | 18 | 1. [Set up the server](#setup) 19 | 20 | 2. [Install the Docker on the server.](#install) 21 | 22 | 3. [Get along with the Golang Docker image](#golang-docker-image) 23 | 24 | * [The ready-to-use Golang env](#golang-env) 25 | 26 | * [The hello world](#hello-world) 27 | 28 | 4. [Build the beego Docker image](#beego-docker-image) 29 | 30 | 5. [Get along with the Mysql Docker image](#mysql-docker-image) 31 | 32 | 6. [Get along with the Nginx Docker image](#nginx-docker-image) 33 | 34 | 7. [Wire dockers up together](#wire-up) 35 | 36 | 8. [Conclusion](#conclusion) 37 | 38 | 39 | 40 | 1. Set up the server 41 | ------------- 42 | 43 | I made this demo on the live server. If you guys want to play on your local machine, you can skip this section. It's perfectly ok, no much difference. 44 | 45 | I bought the virtual host from [Linode](https://www.linode.com/) and install the Ubuntu 14.10. I pointed the subdomain `book.beego.me` to this Ubuntu server. This step is very flexible, Nothing much. 46 | 47 | 48 | 2. Install the Docker on the server. 49 | ------------- 50 | 51 | First of all you need to install the Docker. For different systems it might be different, you can just follow this guide [installation](https://docs.docker.com/installation/#installation) to get Docker installed on your machine no matter what system you are using. 52 | 53 | Specifically for my Ubuntu server: 54 | 55 | ```bash 56 | $ sudo apt-get update 57 | $ sudo apt-get install docker.io 58 | $ source /etc/bash_completion.d/docker.io 59 | ``` 60 | 61 | 3. Get along with the [Golang Docker image](https://registry.hub.docker.com/_/golang/). 62 | ------------- 63 | 64 | Docker has made a [Golang official repo](https://registry.hub.docker.com/_/golang/), you can start using it as your base Go environment. Let's create a hello world web application with our Golang Docker image. 65 | 66 | * ### The ready-to-use Golang env 67 | 68 | Just run this command to start a new Docker Container from the [Golang official repo](https://registry.hub.docker.com/_/golang/) 69 | 70 | `docker run -it golang /bin/bash` 71 | 72 | After the container running, you got the Golang environment: 73 | 74 | `go version` 75 | 76 | You should see something like below. It created a new container from the Golang image and ran command `/bin/bash` with options `-it` on that container which will give you a terminal to interactive with your container. Then I typed `go version` to check my Golang environment. 77 | 78 | ![](../images/docker.golang.png?raw=true) 79 | 80 | The first time when you run `docker run `, if docker can't find it on your local machine, it will automatically try to pull the Docker image from the Docker repository. 81 | 82 | You can check your local Docker images by command: 83 | 84 | `docker images` 85 | 86 | You can check the containers you created from the images by command: 87 | 88 | `docker ps -a` 89 | 90 | 91 | * ### The hello world 92 | 93 | `docker run -it -v /home/leicao/programming/go/hello-world/:/go/src/hello-world golang /bin/bash` 94 | 95 | After the container running, you can build your Go code with the container's Go environment: 96 | 97 | `go build hello.go` 98 | `./hello` 99 | 100 | Below is the result you should see: 101 | 102 | ![](../images/docker.golang.hello-world.png?raw=true) 103 | 104 | Now you might get confused how had this been done, let me explain it detailed: 105 | 106 | In the picture above: 107 | 108 | `/home/leicao/programming/go/hello-world/hello.go` is the hello world file on my host machine which is my Ubuntu running on Linode, it just contains the simple Go hello world code: 109 | 110 | ```go 111 | package main 112 | 113 | import "fmt" 114 | 115 | func main() { 116 | fmt.Println("Hello, 世界") 117 | } 118 | ``` 119 | 120 | Then we run our Docker container with command: 121 | 122 | `docker run -it -v /home/leicao/programming/go/hello-world/:/go/src/hello-world golang /bin/bash` 123 | 124 | We added option `-v /home/leicao/programming/go/hello-world/:/go/src/hello-world`, which will mount the folder `/home/leicao/programming/go/hello-world/` onto my Docker container as the volume `/go/src/hello-world`. 125 | 126 | After this, inside my Docker container, I got the mounted folder `/go/src/hello-world`. Then I can `cd src/hello-world/` to it and `go build hello.go` and finally `./hello`!!! 127 | 128 | Isn't it amazing? With Docker we don't need to try hard to deal with the go environment anymore. Everything is ready to use. 129 | 130 | 131 | 4. Build the [beego Docker image](https://registry.hub.docker.com/u/leicao/beego/). 132 | ------------------- 133 | 134 | Now we have a Golang environment, we can extend it with Beego support. I made a Beego Docker image from the official Golang image, you can get it [here](https://registry.hub.docker.com/u/leicao/beego/) and use it directly. In fact, it's very easy to make your own Docker images. It can be done in 5 minutes. 135 | 136 | We start this with a `Dockerfile`: 137 | 138 | ```dockerfile 139 | # Base image is in https://registry.hub.docker.com/_/golang/ 140 | # Refer to https://blog.golang.org/docker for usage 141 | FROM golang:1.3.3 142 | MAINTAINER Lei Cao lexo.charles@gmail.com 143 | 144 | # ENV GOPATH /go 145 | 146 | # Install beego & bee 147 | RUN go get github.com/astaxie/beego 148 | RUN go get github.com/beego/bee 149 | ``` 150 | 151 | It says we started this image from the official Golang base image `FROM golang:1.3.3`, and then we just go get our dependencies `RUN go get github.com/astaxie/beego` and `RUN go get github.com/beego/bee`. It's quite intuitive. 152 | 153 | How to use this Dockerfile? 154 | 155 | - The first option is build it by your own, run this command inside the folder with your Dockerfile 156 | 157 | `docker build -t leicao/beego .` 158 | 159 | It's simple, right? If it's successfully built, you can see it by `docker images` 160 | 161 | 162 | - The second option is even better which is the Docker's [automated builds](https://docs.docker.com/docker-hub/builds/#the-dockerfile-and-automated-builds) 163 | 164 | It's also very simple, just following the guide, creating your git repo with the Dockerfile, in my case it's [github.com/lei-cao/dockers](https://github.com/lei-cao/dockers). And then I got this [build](https://registry.hub.docker.com/u/leicao/beego/) built by Docker automated builds. 165 | 166 | Now we have the Beego Docker image, let's play with it. 167 | 168 | We can create a brand new beego application with the `bee` tool: 169 | 170 | `docker run --rm -v "$(pwd)":/go/src/ -w /go/src leicao/beego bee new beego-docker` 171 | 172 | `--rm` tells Docker remove this container immediately after executing. `-v "$(pwd)":/go/src/` will mount my current folder on my Ubuntu host to the Docker container `/go/src/. `-w /go/src` tells Docker the working dir. `leicao/beego` is the Docker image we are using to crate the Docker container. And at last, our bee command `bee new beego-docker` to create our beego project `beego-docker`. 173 | 174 | The amazing part here is since we are using mounted volume, so I created the beego project on my host server which is my Ubuntu server by using the Docker image `leicao/beego` we created before. We can working with Golang and Beego without setting up the development environment at all. All we need is our Dockers. 175 | 176 | Then let's run our `beego-docker` project: 177 | 178 | `docker run --rm -v "$(pwd)"/beego-docker:/go/src/beego-docker -w /go/src/beego-docker -p 8081:8080 leicao/beego bee run` 179 | 180 | The new option I am using here is `-p 8081:8080` which will expose the port 8080 inside the Docker container to the host's 8081 port. And guess what, see it on live!! [http://book.beego.me:8081](http://book.beego.me:8081). 181 | 182 | ![](../images/docker.bee.run.png?raw=true) 183 | 184 | 185 | 5. Get along with the [Mysql Docker image](https://registry.hub.docker.com/_/mysql). 186 | ------------------ 187 | 188 | Similar as the Golang Docker image we play with, we can get the Mysql running in a single command: 189 | 190 | `docker run --name db -e MYSQL_ROOT_PASSWORD=root -d mysql` 191 | 192 | This command will pull the official mysql Docker image from the repo if needed and start a mysql service container which is named `db`. I set this mysql server's root password to `root` by the option `-e MYSQL_ROOT_PASSWORD=root`. 193 | 194 | To connect to this server from command line: 195 | 196 | `docker run -it --link db:mysql mysql sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p'` 197 | 198 | This command will connect to the mysql server we ran before by using `--link db:mysql` which links by the container name `db`. And we can create our database here `create database beego;` 199 | 200 | ![](../images/docker.mysql.png?raw=true) 201 | 202 | > You can check the [Docker document about links](http://docs.docker.com/userguide/dockerlinks/) for more information. 203 | 204 | 6. Get along with the [Nginx Docker image](https://registry.hub.docker.com/_/nginx). 205 | ------------------ 206 | 207 | And one more thing, Nginx. 208 | 209 | With our Beego application running by: 210 | 211 | `docker run --rm --name beego -v "$(pwd)"/beego-docker:/go/src/beego-docker -w /go/src/beego-docker -p 8080:8080 leicao/beego bee run` 212 | 213 | And with this nginx proxy config: 214 | 215 | ``` 216 | server { 217 | listen 80; 218 | server_name book.beego.me; 219 | 220 | client_max_body_size 20m; 221 | client_header_timeout 1200; 222 | client_body_timeout 1200; 223 | send_timeout 1200; 224 | keepalive_timeout 1200; 225 | 226 | location / { 227 | proxy_pass http://beego:8080; 228 | } 229 | } 230 | 231 | ``` 232 | 233 | We can start our nginx by: 234 | 235 | `docker run --name nginx --link beego:beego -v "$(pwd)"/nginx:/etc/nginx/conf.d/ -p 80:80 -d nginx` 236 | 237 | You might be wondering why it's `proxy_pass http://beego:8080;` here. It happens magically by using `--link beego:beego`, Docker will set a host which pointing container beego's ip to the container's name, in the nginx's `/etc/hosts` file, 238 | 239 | ![](../images/docker.link.hosts.png?raw=true) 240 | 241 | 7. Wire dockers up together 242 | ----------------- 243 | 244 | We are having Golang, Beego/Bee, Mysql, Nginx now. Let's start a new api project similar as the tutorial here [bee api application](http://beego.me/blog/beego_api) Video: [Youtube](http://youtu.be/w7RziV_Sn-g). 245 | 246 | We will create a beeblog api and [Here is our database schema](medias/beeblog.sql) 247 | 248 | - Start a Mysql server with db name beeblog: 249 | 250 | `docker run --name db -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=beeblog -d mysql` 251 | 252 | 253 | - Import our database schema: 254 | 255 | `docker run --rm --link db:mysql -v "$(pwd)"/mysql:/home/mysql -it mysql sh -c 'exec mysql -h"$MYSQL_PORT_3306_TCP_ADDR" -P"$MYSQL_PORT_3306_TCP_PORT" -uroot -p beeblog < /home/mysql/beeblog.sql'` 256 | 257 | 258 | - Generate the api application: 259 | 260 | `docker run --rm --link db:mysql -v "$(pwd)":/go/src/ -w /go/src leicao/beego bee api beeblog -conn="root:root@tcp(mysql:3306)/beeblog"` 261 | 262 | 263 | - Run our api application: 264 | 265 | `docker run --rm --link db:mysql -v "$(pwd)/beeblog":/go/src/beeblog -w /go/src/beeblog -p 8080:8080 --name beego leicao/beego bee run -downdoc=true -gendoc=true` 266 | 267 | Or run it directly 268 | `sudo docker run --link db:mysql -v "$(pwd)/beeblog":/go/src/beeblog -w /go/src/beeblog -p 8080:8080 --name beego -d leicao/beego ./beeblog` 269 | 270 | - Start the Nginx: 271 | 272 | `docker run --name nginx --link beego:beego -v "$(pwd)"/nginx:/etc/nginx/conf.d/ -p 80:80 -d nginx` 273 | 274 | Now you can see our beeblog api on live: 275 | 276 | The API: [http://book.beego.me/v1/posts](http://book.beego.me/v1/posts) 277 | 278 | And the document: [http://book.beego.me/swagger/swagger-1/](http://book.beego.me/swagger/swagger-1/) 279 | 280 | ![](../images/docker.swagger.png?raw=true) 281 | 282 | 8. [Conclusion](#conclusion) 283 | ----------- 284 | 285 | This section gives you a basic version of Docker, Docker images, Docker containers and how to work with them. We don't need to spend so much time to set up the development environment and suffer the dependency hell anymore. At the beginning you might be afraid of all those long docker commands you need to type, just go check the Docker document and you will get familiar with them soon. 286 | 287 | Also there are some convenient tools to help you out from these commands. [Fig](http://www.fig.sh) is one of them, it gives you fast and isolated development environments using Docker. I will talk about Fig in the future. 288 | 289 | 290 | 291 | 292 | 293 | --------------------------------------------------------------------------------