├── 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 |  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 |
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 |