常见问题
最后更新于:2022-04-01 04:22:41
> 如何将已有的http.Handlers整合到Revel中?
在[概念图](http://gorevel.cn/docs/manual/concepts.html)中, http.Handler 用于处理用户的请求。Revel的处理是非常简单的,它只是创建控制器实例,并将请求传递给过滤器链。
应用程序可以通过重写默认的处理程序整合现有http.Handlers:
~~~
func installHandlers() {
var (
serveMux = http.NewServeMux()
revelHandler = revel.Server.Handler
)
serveMux.Handle("/", revelHandler)
serveMux.Handle("/path", myHandler)
revel.Server.Handler = serveMux
}
func init() {
revel.OnAppStart(installHandlers)
}
~~~
> 拦截器、过滤器和模块之间是什么关系?
1. 模块是可以插入到程序中的包。他们可以在多个Revel程序(或第三方源)中共享控制器、视图、资源和其他代码。
2. 过滤器是可挂接到请求处理管道的函数。他们一般作为一个整体处理技术在应用程序中使用,来垂直分隔应用程序逻辑。
3. 拦截器是封装数据和行为一种方便的方式,因为嵌入类型导入它的拦截器和字段。这使得拦截器可以很好的处理一些事情,比如验证登录cookie并保存这些信息到一个字段。拦截器可以应用到一个或多个控制器。
命令行工具
最后更新于:2022-04-01 04:22:39
## Build and Run
为了使用Revel,必须构建Revel命令行工具:
~~~
$ go get github.com/revel/revel/revel
~~~
现在运行它:
~~~
$ bin/revel
~
~ revel! http://revel.github.com/revel
~
usage: revel command [arguments]
The commands are:
new create a skeleton Revel application
run run a Revel application
build build a Revel application (e.g. for deployment)
package package a Revel application (e.g. for deployment)
clean clean a Revel application's temp files
test run all tests from the command-line
Use "revel help [command]" for more information.
~~~
关于每条命令的含义,请参阅该工具的内置帮助功能。
app.conf
最后更新于:2022-04-01 04:22:37
## 概述
`app.conf` 是Revel程序的配置文件,它使用 [goconfig](https://github.com/revel/config) 语法,类似微软的 INI 文件。
下面是个例子:
~~~
app.name=chat
app.secret=pJLzyoiDe17L36mytqC912j81PfTiolHm1veQK6Grn1En3YFdB5lvEHVTwFEaWvj
http.addr=
http.port=9000
[dev]
results.pretty=true
watch=true
log.trace.output = off
log.info.output = stderr
log.warn.output = stderr
log.error.output = stderr
[prod]
results.pretty=false
watch=false
log.trace.output = off
log.info.output = off
log.warn.output = %(app.name)s.log
log.error.output = %(app.name)s.log
~~~
每个段是一种 **运行模式**。最上面的 key (不在任何段内)对所有的运行模式有效。这使得默认值在所有模式中适用,并且根据需要被重写。`[prod]` 段仅用于 `生产` 模式。
新建的Revel程序中默认定义了 **dev** 和 **prod** 模式, 你也可以自定义你需要的段。 程序启动时,根据 ([命令行工具](http://gorevel.cn/docs/manual/tool.html))“revel run” 提供的参数来选择运行模式。
## 自定义属性
开发者可以自定义key,并通过 [`revel.Config` 变量](http://gorevel.cn/docs/docs/godoc/revel.html#variables) 访问它们。这里公开了一些简单的 [api](http://gorevel.cn/docs/docs/godoc/config.html)。
## 内建属性
### 应用程序设置
#### app.name
应用程序名称,用于控制台输出和开发web页。
例如:
~~~
app.name = Booking example application
~~~
默认值: 无值
* * *
#### app.secret
密钥用于密码操作 ([`revel.Sign`](http://gorevel.cn/docs/docs/godoc/util.html#Sign))。Revel 在内部使用它签署session cookies。设置为空将禁用签名。
使用 `revel new`新建项目时,它被设置为一个随机的字符串。
例如:
~~~
app.secret = pJLzyoiDe17L36mytqC912j81PfTiolHm1veQK6Grn1En3YFdB5lvEHVTwFEaWvj
~~~
默认值: 无值
### HTTP settings
#### http.port
监听端口
例如:
~~~
http.port = 9000
~~~
* * *
#### http.addr
监听ip地址
Linux中, 空字符串代表通配符 – Windows中, 空字符串被转换为 `"localhost"`
默认值: ””
* * *
#### harness.port
Specifies the port for the application to listen on, when run by the harness. For example, when the harness is running, it will listen on `http.port`, run the application on `harness.port`, and reverse-proxy requests. Without the harness, the application listens on `http.port` directly.
默认情况下,会选择一个随机的空闲端口。这仅仅是必要的,由该程序限制插座访问的环境中运行时设置。By default, a random free port will be chosen. This is only necessary to set when running in an environment that restricts socket access by the program.
Default: 0
* * *
#### http.ssl
如果为真, Revel Web服务器将自行配置为接受SSL连接。这需要一个 X509 证书和一个 key 文件。
默认值: false
#### http.sslcert
指定 X509 证书文件的路径
默认值: ””
#### http.sslkey
指定 X509 证书 key的路径
默认值: ””
### 响应结果
#### results.chunked
确定模板渲染时是否使用 [分块编码](http://gorevel.cn/docs/manual/en.wikipedia.org/wiki/Chunked_transfer_encoding)。分块编码可以减少发送到客户端的第一个字节的时间(在整个模板已经完全呈现数据之前)。
默认值: false
* * *
#### results.pretty
配置 [`RenderXml`](http://gorevel.cn/docs/docs/godoc/controller.html#RenderXml) 和 [`RenderJson`](http://gorevel.cn/docs/docs/godoc/controller.html#RenderJson) 生成缩进格式的 XML/JSON. 例如:
~~~
results.pretty = true
~~~
默认值: false
### 国际化 (i18n)
#### i18n.default_language
为消息翻译指定默认的语言,如果客户端请求的语言环境未确认。如果不指定,则返回一个虚拟的信息。
例如:
~~~
i18n.default_language = en
~~~
默认值: ””
* * *
#### i18n.cookie
指定存储用户语言环境的cookie名称
默认值: “%(cookie.prefix)_LANG” (参考 cookie.prefix)
### 监视
Revel 监视项目改动,并支持几种类型文件的热重载。启用监视:
~~~
watch = true
~~~
如果为假, 禁用监视, 并忽略其他相关的监视配置 `watch.*` (适用于生产环境)
默认值: true
* * *
#### watch.templates
如果为真, Revel 监视模板变化,必要时重新加载他们。
默认值: true
* * *
#### watch.routes
如果为真, Revel 监视 `routes` 文件的变化,必要时重新加载。
默认值: true
* * *
#### watch.code
如果为真, Revel 监视Go代码改动,必要时重新编译代码(作为反向代理运行)。
`app/` 目录(包括子目录)下的代码都被监视。
默认值: true
### Cookies
Revel 组件默认使用下面的 cookies:
* REVEL_SESSION
* REVEL_LANG
* REVEL_FLASH
* REVEL_ERRORS
#### cookie.prefix
Revel 使用这个属性作为 Revel-produced cookies前缀。这样可以在同一台主机上运行多个REVEL应用程序。
例如,
~~~
cookie.prefix = MY
~~~
则对应的 cookie 名称如下:
* MY_SESSION
* MY_LANG
* MY_FLASH
* MY_ERRORS
默认值: “REVEL”
### Session
#### session.expires
Revel 使用这个属性设置session cookie的有效期。 Revel 使用 [ParseDuration](http://golang.org/pkg/time/#ParseDuration) 解析字符串,默认值是 30 天。也可以设置为会话结束时过期。 请注意,客户端的行为依赖于浏览器的设置,所以结果并不总是保证。
### 模板
#### template.delimiters
指定模板左右分隔符
必须这样指定分隔符 “左分隔符 右分隔符”
默认值: “{{ }}”
### 格式化
#### format.date
指定默认的日期格式,Revel在两个地方使用它:
* 绑定日期参数到 `time.Time` (参考 [binding](http://gorevel.cn/docs/manual/binding.html))
* 在模板中使用 `date` 模板函数输出日期 (参考 [模板函数](http://gorevel.cn/docs/manual/templates.html))
默认值: “2006-01-02”
* * *
#### format.datetime
指定默认的日期时间格式,Revel在两个地方使用它:
* 绑定日期参数到 `time.Time` (参考 [binding](http://gorevel.cn/docs/manual/binding.html))
* 在模板中使用 `datetime` 模板函数输出日期 (参考 [模板函数](http://gorevel.cn/docs/manual/templates.html))
默认值: “2006-01-02 15:04”
### 数据库
#### db.import
指定DB模块的 database/sql 驱动程序导入路径。
默认值: ””
* * *
#### db.driver
指定 database/sql 驱动程序名称 (在[`sql.Open`](http://golang.org/pkg/database/sql/#Open)中使用).
默认值: ””
* * *
#### db.spec
指定 database/sql 数据源名称 (在 [`sql.Open`](http://golang.org/pkg/database/sql/#Open)中使用).
默认值: ””
### 构建
#### build.tags
[Build tags](http://golang.org/cmd/go/#Compile_packages_and_dependencies) 构建程序的时候使用。
默认值: ””
### 日志
TODO
### 缓存
[cache](http://gorevel.cn/docs/manual/cache.html) 模块是一个简单的堆或分布式缓存接口
#### cache.expires
设置缓存过期时间。在程序中调用者使用常量`cache.DEFAULT`获取。
它是接受一个[`time.ParseDuration`](http://golang.org/pkg/time/#ParseDuration) 字符串。
(目前还不能指定默认值为 `FOREVER`)
默认值: “1h” (1 小时)
* * *
#### cache.memcached
如果为真, 缓存模块使用 [memcached](http://memcached.org/) 来代替内存缓存。
默认值: false
* * *
#### cache.hosts
一个逗号分隔的 memcached 主机列表。缓存条目使用确定的主机名缓存key自动分片到可用的主机中。主机可能会多次列出,以增加共享的缓存空间。
默认值: ””
### 计划任务
[计划任务](http://gorevel.cn/docs/manual/jobs.html) 模块允许你运行计划任务或者临时任务
#### 时间表
时间表可以通过key来配置。
~~~
cron.schedulename = @hourly
~~~
时间表的计划时间可以在执行器中提交任务时使用。例如:
~~~
jobs.Schedule("cron.schedulename", job)
~~~
* * *
#### jobs.pool
允许同时允许的任务数量。例如:
~~~
jobs.pool = 4
~~~
如果为 0, 则没有数量限制
默认值: 10
* * *
#### jobs.selfconcurrent
如果为真, 允许一个任务运行,即使是该任务的实例仍在进行中。
默认值: false
### 模块
[模块](http://gorevel.cn/docs/manual/modules.html) 通过指定导入路径将模块添加到应用程序中。例如:
~~~
module.testrunner = github.com/revel/revel/modules/testrunner
~~~
## 开发计划
* 允许使用命令行参数配置值或以其他方式在命令行中指定值。
参考
最后更新于:2022-04-01 04:22:34
部署
最后更新于:2022-04-01 04:22:32
## 概要
几种常见的部署方法如下:
1. 本地编译代码,然后复制到服务器上运行
2. 在服务器上拉取代码,然后编译、运行
3. 使用 Heroku 进行部署
使用命令行演示互动部署 - 一般将web服务器作为守护程序运行。常用工具有:
* [Ubuntu Upstart](http://upstart.ubuntu.com/)
* [systemd](http://www.freedesktop.org/wiki/Software/systemd)
## 本地编译
目标机器上不需要安装Go语言环境。[Revel命令行工具](http://gorevel.cn/docs/manual/tool.html) 提供了`package`包命令,用来编译和压缩应用程序, 如下所示:
~~~
# 本地测试运行
$ revel run import/path/to/app
.. 测试程序 ..
# 打包程序
$ revel package import/path/to/app
打包的文件准备好了: app.tar.gz
# 复制到目标机器
$ scp app.tar.gz target:/srv/
# 在目标机器上运行
$ ssh target
$ cd /srv/
$ tar xzvf app.tar.gz
$ bash run.sh
~~~
如果您的本地机器与目标机器的架构相同,那么不会有什么问题。否则,你需要参考交叉编译来构建指定平台架构的程序。
### 增量部署
由于静态链接的二进制程序带有完整的资源文件,可能会变得相当大,所以支持增量部署。
~~~
# 构建应用程序到一个临时目录
$ revel build import/path/to/app /tmp/app
# 将临时目录 Rsync 到服务器上的主目录
$ rsync -vaz --rsh="ssh" /tmp/app server
# 连接到服务器,并重新启动应用程序
...
~~~
Rsync 支持ssh完整的复制操作。例如, 下面是一个更复杂的操作:
~~~
# 一个使用自定义证书、登录名和目标目录的例子
$ rsync -vaz --rsh="ssh -i .ssh/go.pem" /tmp/myapp2 ubuntu@ec2-50-16-80-4.compute-1.amazonaws.com:~/rsync
~~~
## 在服务器上构建
这种方法依赖你的版本控制系统来分发、更新代码。你需要在服务器上安装Go语言环境。好处是,你避免了潜在的交叉编译。
~~~
$ ssh server
... 安装Go语言环境 ...
... 配置存储库 ...
# 进入你的应用程序所在的目录 (GOPATH环境变量), 拉取代码, 并运行。
$ cd gocode/src/import/path/to/app
$ git pull
$ revel run import/path/to/app prod
~~~
## Heroku
Revel 维护了一个 [Heroku Buildpack](https://github.com/revel/heroku-buildpack-go-revel), 允许一条命令即可部署代码,请参考 [自述文件](https://github.com/revel/heroku-buildpack-go-revel/blob/master/README.md) 使用说明
## 交叉编译
为了创建一个交叉编译环境,我们需要从源代码构建。参考 [从源代码安装Go](http://golang.org/doc/install/source) 获取更多信息。你必须正确设置 $PATH 和 $GOPATH 环境变量, 否则,如果已经有一个二进制的Go语言包存在,你会陷入严重的错误。
当Go编译器安装成功后,通过指定 GOOS 和 GOARCH 目标环境来建立交叉编译环境。参考 [可用的环境变量](http://golang.org/doc/install/source#environment) 获取更多信息。
~~~
$ cd /path/to/goroot/src
$ GOOS=linux GOARCH=amd64 ./make.bash --no-clean
$ GOOS=windows GOARCH=386 ./make.bash --no-clean
~~~
在新的环境中安装Revel,然后设定目标架构,打包应用程序。
~~~
$ GOOS=linux GOARCH=amd64 revel package import/path/to/app
~~~
然后,将压缩包复制到目标平台。
日志
最后更新于:2022-04-01 04:22:30
Revel 支持四类日志信息:
* TRACE - 调试信息
* INFO - 一般信息
* WARN - 警告信息
* ERROR - 错误信息
下面是在Revel中使用日志的例子:
~~~
now := time.Now()
revel.TRACE.Printf("%s", now.String())
~~~
日志记录器默认使用 [go 日志](http://golang.org/pkg/log/).
日志记录器在 [app.conf](http://gorevel.cn/docs/manual/appconf.html)中配置。例如:
~~~
app.name = sampleapp
[dev]
log.trace.output = stdout
log.info.output = stdout
log.warn.output = stderr
log.error.output = stderr
log.trace.prefix = "TRACE "
log.info.prefix = "INFO "
log.trace.flags = 10
log.info.flags = 10
[prod]
log.trace.output = off
log.info.output = off
log.warn.output = log/%(app.name)s.log
log.error.output = log/%(app.name)s.log
~~~
在**开发**环境中:
* 显示详细日志
* **info** 或 **trace**信息以app.conf中定义的前缀显示信息
在**生产**环境中:
* **info** 和 **trace** 日志将被忽略
* 警告 和 错误信息被写入 **log/sampleapp.log** 文件
根据 [标记常量](http://www.golang.org/pkg/log/#constants)修改日志格式,。例如, `01:23:23 /a/b/c/d.go:23 Message` 格式,使用标记 `Ltime | Llongfile = 2 | 8 = 10`
开发状态:
* 如果日志目录log不存在,Revel 会自动创建日志目录。
版本
最后更新于:2022-04-01 04:22:28
# 版本控制
nathany上已经给出了Go包版本控制的许多信息。然而, 那时还没有一个包版本管理的社区标准。因此, 只能由开发者确保软件安全与可重复构建。
如果你使用Revel构建应用程序, 开发者应避免由于不兼容造成的问题。你的构建过程不应当使用go get获取Revel的主分支。
处理这种情况最简单的方法是签出Revel和所有依赖包到你的代码库中。如果你使用git, 可以把这些库作为你项目的子代码库。
或者, 试试下面链接中的软件包管理器。
* [Go包版本控制](http://nathany.com/go-packages/)
操作
最后更新于:2022-04-01 04:22:25
计划任务
最后更新于:2022-04-01 04:22:23
Revel 支持计划任务(异步执行), 运行在请求流程的外部。比如,更新缓存数据的周期性任务,或发送电子邮件的临时任务。
## 激活
该框架是一个可选模块,默认是禁用的。要将它激活,需要在配置文件中添加该模块:
~~~
module.jobs = github.com/revel/revel/modules/jobs
~~~
此外,为了访问计划任务的监控页面,需要将下面的内容添加到路由文件中:
~~~
module:jobs
~~~
这条语句将插入 `/@jobs` 路由
## 选项
有两个选项来限制计划任务。
这个例子显示了它们的默认值。
~~~
jobs.pool = 10 # 允许同时运行的任务数
jobs.selfconcurrent = false # 一个任务只允许一个实例
~~~
## 启动计划任务
应用程序启动时, 使用[`revel.OnAppStart`](http://gorevel.cn/docs/docs/godoc/init.html#OnAppStart) 注册一个函数来运行一个任务。Revel 在服务启动之前,会连续启动这些任务。 请注意,此功能实际上并未使用计划任务模块,它被用来提交任务,但并不阻止服务器的启动。
~~~
func init() {
revel.OnAppStart(func() { jobs.Now(populateCache{}) })
}
~~~
## 周期性任务
任务可以被指定在任意时间运行。使用的时间表有两个选项:
1. 一个cron时间格式
2. 固定时间间隔
Revel 使用 [cron library](https://github.com/revel/cron) 来解析时间表和任务。[cron库的说明](https://github.com/revel/cron/blob/master/README.md) 提供了时间格式的详细描述。
计划任务通常使用 [`revel.OnAppStart`](http://gorevel.cn/docs/docs/godoc/init.html#OnAppStart) 钩子进行注册,但也可以在以后的任何时间注册。
下面是一些例子:
~~~
import (
"github.com/revel/revel"
"github.com/revel/revel/modules/jobs/app/jobs"
"time"
)
type ReminderEmails struct {
// 过滤
}
func (e ReminderEmails) Run() {
// 查询数据库
// 发送电子邮件
}
func init() {
revel.OnAppStart(func() {
jobs.Schedule("0 0 0 * * ?", ReminderEmails{})
jobs.Schedule("@midnight", ReminderEmails{})
jobs.Schedule("@every 24h", ReminderEmails{})
jobs.Every(24 * time.Hour, ReminderEmails{})
})
}
~~~
## 时间表
您可以在 `app.conf`文件中配置时间表,并在任何地方引用。这可以为 crontab 格式提供易于重用和有用的说明。
在 `app.conf`定义你的时间表,:
~~~
cron.workhours_15m = 0 */15 9-17 ? * MON-FRI
~~~
使用一个cron规范指定时间表,就可以在任何地方引用它。
~~~
func init() {
revel.OnAppStart(func() {
jobs.Schedule("cron.workhours_15m", ReminderEmails{})
})
}
~~~
> 注意: cron 时间表的名字必须以 “cron.”开头
## 临时任务
有时候在响应用户的一个操作时,还要处理一些事情。在这种情况下,模块可以在某个时间运行一个任务。
模块提供的唯一控制是等待多长时间运行任务。
~~~
type AppController struct { *revel.Controller }
func (c AppController) Action() revel.Result {
// 处理请求
...
// 立即发送电子邮件(异步)
jobs.Now(SendConfirmationEmail{})
// 或者,一分钟后发送电子邮件(异步)。
jobs.In(time.Minute, SendConfirmationEmail{})
}
~~~
## 注册函数
通过使用`jobs.Func`类型包装一个`func()`函数,来注册一个任务。例如:
~~~
func sendReminderEmails() {
// 查询数据库
// 发送电子邮件
}
func init() {
revel.OnAppStart(func() {
jobs.Schedule("@midnight", jobs.Func(sendReminderEmails))
})
}
~~~
## 任务状态
模块提供了一个状态页面,用来显示任务的运行状态(**IDLE** 或 **RUNNING**), 以及之前和下次运行时间。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-20_5625fc00ebef6.png)
为了安全起见,状态页面仅接受127.0.0.1发起的请求。
## 限制池的大小
任务模块配置,用来限制同一时间运行的任务数量。这允许开发人员限制可能会潜在地使用异步任务的资源 - 通常是交互式的响应高于异步处理。当池中正在运行的任务已满,新的任务将阻塞,直到正在运行的任务完成。
注意事项:计划任务在一个channel上阻塞,为等待的goroutines实现了一个FIFO (但没有指定/需要这样). [参考这里的讨论](https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/CPwv8WlqKag).
## 开发状态
* 允许使用HTTP基本身份验证凭据访问任务状态页面
* 允许管理员从状态页面以交互方式执行预定任务
* 支持更多的页面属性,例如:池的大小,任务队列长度等。
测试
最后更新于:2022-04-01 04:22:21
Revel提供了一个测试框架,可以很容易地编写和运行针对您的应用程序的功能测试。
应用程序带有一个简单的测试骨架以便快速上手测试。
## 概要
测试代码保存在测试目录中:
~~~
corp/myapp
app/
conf/
public/
tests/ <----
~~~
一个简单的测试如下所示:
~~~
type AppTest struct {
revel.TestSuite
}
func (t *AppTest) Before() {
println("Set up")
}
func (t *AppTest) TestThatIndexPageWorks() {
t.Get("/")
t.AssertOk()
t.AssertContentType("text/html")
}
func (t *AppTest) After() {
println("Tear down")
}
~~~
上面的例子中展示了:
* 测试套件是嵌入`revel.TestSuite`的一个struct
* `Before()` 、 `After()` 在每个测试方法之前和之后被调用,如果有的话。
* `revel.TestSuite` 帮助发出请求到你的应用程序,和对响应的断言。
* 如果一个断言失败,产生了恐慌,将被测试工具捕获。
你可以用两种方式运行这个测试:
* 交互方式,Web浏览器,开发过程中非常有用。
* 非交互方式,命令行,对于持续构建整合有用。
## 开发一个测试套件
要创建自己的测试套件,需要定义一个嵌入了`revel.TestSuite`类型的struct, 它提供了一个HTTP客户端和一些辅助方法发出请求到应用程序。
~~~
type TestSuite struct {
Client *http.Client
Response *http.Response
ResponseBody []byte
}
// 一些请求方法
func (t *TestSuite) Get(path string)
func (t *TestSuite) Post(path string, contentType string, reader io.Reader)
func (t *TestSuite) PostForm(path string, data url.Values)
func (t *TestSuite) MakeRequest(req *http.Request)
// 一些断言方法
func (t *TestSuite) AssertOk()
func (t *TestSuite) AssertContentType(contentType string)
func (t *TestSuite) Assert(exp bool)
func (t *TestSuite) Assertf(exp bool, formatStr string, args ...interface{})
~~~
[参考godoc](http://gorevel.cn/docs/docs/godoc/tests.html)
所有的请求方法类似:
1. 接受一个路径 (比如 `/users/`)
2. 向应用程序服务器发出一个请求
3. 存储 `Response` 中的成员
4. 读取完整的响应到`ResponseBody` 成员中
如果开发人员希望使用一个定制的HTTP客户端,而不是默认的[http.DefaultClient](http://golang.org/pkg/net/http/#pkg-variables),应当在`Before()` 方法之前替换它。
断言失败后,会抛出恐慌并被测试工具捕获,并将错误列出。
## 运行测试套件
为了运行测试,`testrunner` 模块必须被激活。需要在 `app.conf`文件中配置:
~~~
module.testrunner = github.com/revel/revel/modules/testrunner
~~~
您还必须导入测试模块的路由,在你的 `routes` 文件中加入下面的内容:
~~~
module:testrunner
~~~
配置完后,测试就可以交互或非交互方式运行。
### 运行交互式测试
要利用 Revel 的热编译功能,交互式测试运行提供了快速编辑刷新周期。
例如,开发人员从浏览器中访问 `/@tests`:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-20_5625fc0008afb.png)
然后,增加一个测试方法:
~~~
func (t AppTest) TestSomethingImportant() {
t.Get("/")
t.AssertOk()
t.AssertContentType("text/xml")
}
~~~
然后,刷新浏览器,看看新的测试:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-20_5625fc0022c0a.png)
运行测试:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-20_5625fc0035a1c.png)
嗯哼,,,行不通哦,,,修改代码使用“text/html” 替换 “text/xml”类型。
~~~
t.AssertContentType("text/html")
~~~
然后,重新运行测试:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-10-20_5625fc005745a.png)
成功啦!
### 运行非交互式测试
Revel [命令行工具](http://gorevel.cn/docs/manual/tool.html) 提供了一个 `test` 命令,允许在命令行中运行测试。
下面是一个示例会话:
~~~
$ revel test github.com/revel/revel/samples/booking dev
~
~ revel! http://revel.github.com/revel
~
INFO 2012/11/09 19:21:02 revel.go:237: Loaded module testrunner
Open DB
Listening on port 9000...
INFO 2012/11/09 19:21:06 test.go:95: Testing Booking example (github.com/revel/revel/samples/booking) in dev mode
Go to /@tests to run the tests.
1 test suite to run.
AppTest PASSED 0s
All Tests Passed.
~~~
您还可以运行单个测试套件,或套件内的方法,用句点分隔参数:
~~~
$ revel test github.com/revel/revel/samples/booking dev ApplicationTest
$ revel test github.com/revel/revel/samples/booking dev ApplicationTest.TestThatIndexPageWorks
~~~
在控制台测试套件只有一个简单的合格/不合格显示。更详细的结果写入到文件系统:
~~~
$ cd src/github.com/revel/revel/samples/booking
$ find test-results
test-results
test-results/app.log
test-results/AppTest.passed.html
test-results/result.passed
~~~
它写三点不同:
1. 应用程序的标准输出和标准错误重定向到 `app.log`
2. 每个测试套件有一个HTML文件被写入,说明测试通过或失败。
3. 无论 `result.passed` 或 `result.failed` 被写入, 这取决于整体的成功。
对于整合持续构建测试,有两点建议:
1. 检查返回码,0代表测试成功,否则为非0值。
2. 测试运行后要求存在 `result.success`, 或禁止 `result.failed`存在。
## 注意事项
Revel 做了什么:
* 扫描嵌入TestSuite类型 (transitively) 的源代码
* 在生成的 main.go 文件中,为 `revel.TestSuites` 类型的变量设置一个列表
* 按要求,使用反射来查找所有以“Test”开头的TestSuite类型的方法,并调用它们来运行测试。
* 从错误或失败的断言捕获恐慌,显示错误。
当 `testrunner` 模块激活后,测试代码才会被构建。
## 开发计划
改进测试框架:
* 固定存储测试数据。
* 日志写入到一个文件中(而不是 stderr / stdout)也应该被重定向到 `test-results/app.log`
概要
最后更新于:2022-04-01 04:22:19
# 模块
模块是一些包,可以集成到Revel程序中。Revel允许多个Revel程序(或第三方代码)共享控制器、模板、资源和其他代码。
模块中文件的布局应当与Revel应用程序文件结构一致。“托管”应用程序会按以下方式将它们合并:
1. module/app/views 的所有模板,会被添加到模板加载器的搜索路径中
2. module/app/controllers 的所有控制器, 将被视为你的应用程序中的控制器。
3. 资源文件通过 `Static.ServeModule("modulename","public")` 提供
4. 路由通过 `module:modulename` 被添加到你的程序中
### 启用一个模块
为了将模块添加到您的应用程序,需要在`app.conf` 中添加一行配置:
~~~
module.mymodulename = go/import/path/to/module
~~~
如果导入路径为空,将禁用模块:
~~~
module.mymodulename =
~~~
举个栗子, 启用测试运行模块:
~~~
module.testrunner = github.com/revel/revel/modules/testrunner
~~~
模块
最后更新于:2022-04-01 04:22:16
Cache
最后更新于:2022-04-01 04:22:14
Revel 提供了一个服务器端、临时的、低延迟存储的缓存库。对于频繁访问数据库中缓慢变化的数据,使用缓存一个很好的方法,并且它也可以用于实现用户session (如果基于cookie的session不足).
参考 [缓存接口](http://godoc.org/github.com/revel/revel/cache#Cache)
## 过期时间
缓存有三种过期时间:
* time.Duration,指定一个过期时间。
* `cache.DEFAULT`, 默认过期时间(1小时)。
* `cache.FOREVER`, 永不过期。
重要提示:调用者不能依赖于存在于缓存中内容,因为数据是不持久保存,重新启动后,会清除所有缓存数据。
## 序列化
缓存读写接口自动为调用者序列化任何类型的的值。有以下几种方式:
* 如果是 `[]byte`类型, 数据保持不变。
* 如果是 整数类型, 数据被保存为 ASCII。
* 其他类型,使用 [`encoding/gob`](http://golang.org/pkg/encoding/gob/)进行编码。
## 实现
缓存通过以下几种方式进行配置:
* 分布式[memcached](http://memcached.org/) 主机缓存
* 单独 [redis](http://redis.io/) 主机缓存
* 本地内存缓存
## 配置
在`app.conf`中配置缓存:
* `cache.expires` - 一个字符串,[`time.ParseDuration`](http://golang.org/pkg/time/#ParseDuration) 类型,指定一个过期时间 (默认1小时)
* `cache.memcached` -一个布尔值,是否开启memcached缓存 (默认不开启)
* `cache.redis` -一个布尔值,是否开启redis缓存 (默认不开启)
* `cache.hosts` - 缓存的主机列表(多个主机使用逗号分隔),`cache.memcached`开启后有效。
## 例如
下面是常见操作的一个例子。请注意,如果不需要调用的结果来处理请求,调用者可以在新的goroutine调用缓存操作。
~~~
import (
"github.com/revel/revel"
"github.com/revel/revel/cache"
)
func (c App) ShowProduct(id string) revel.Result {
var product Product
if err := cache.Get("product_"+id, &product); err != nil {
product = loadProduct(id)
go cache.Set("product_"+id, product, 30*time.Minute)
}
return c.Render(product)
}
func (c App) AddProduct(name string, price int) revel.Result {
product := NewProduct(name, price)
product.Save()
return c.Redirect("/products/%d", product.id)
}
func (c App) EditProduct(id, name string, price int) revel.Result {
product := loadProduct(id)
product.name = name
product.price = price
go cache.Set("product_"+id, product, 30*time.Minute)
return c.Redirect("/products/%d", id)
}
func (c App) DeleteProduct(id string) revel.Result {
product := loadProduct(id)
product.Delete()
go cache.Delete("product_"+id)
return c.Redirect("/products")
}
~~~
## Session 用法
缓存有一个全球性的key空间 - 使用它作为一个session存储,调用方应采用session UUID的优点,如下图所示:
~~~
cache.Set(c.Session.Id(), products)
// 然后在随后的请求中使用它
err := cache.Get(c.Session.Id(), &products)
~~~
Messages
最后更新于:2022-04-01 04:22:12
Revel使用文本文件提供国际化翻译支持。Revel 支持语言翻译文件化, 自动区域查询, cookie重写、嵌套的消息与参数。
#### 词汇表
* Locale(语言环境): 包含 *语言* 和 *区域*两个部分,指示用户的语言偏好,例如 `en-US`。
* Language(语言): locale 的语言部分, 例如 `en`。 语言标识符请参考 [ISO 639-1 codes](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)。
* Region(区域): locale 的区域部分, 例如 `US`。 区域标识符请参考 [ISO 3166-1 alpha-2 codes](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)。
## 示例程序
Revel 处理消息文件和国际化的方法与其他框架类似。如果你想立马上手, 可以参考示例程序 `revel/samples/i18n`,里面包含所有的基础知识。
## 消息文件
国际化消息在消息文件中定义。这些消息在渲染视图模板(或程序的其他地方)时使用。当创建新的消息文件时,需要遵循几个规则:
* 所有的消息文件应存放在应用程序根目录的`messages`目录中。
* 文件扩展名与 *language* 一致,并且应为 [ISO 639-1 code](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)。
* 消息文件应该是 UTF-8 编码。虽然不是强制规定, 最好还是遵循这个约定。
* 消息文件必须是 [goconfig 文件](https://github.com/revel/config) 支持多有的 goconfig 特性
### 组织消息文件
消息文件的文件名没有任何限制; 只要扩展名合法就行。每种语言的文件数量也没有限制。当应用程序启动时,Revel会解析`messages`语言目录中所有的扩展名合法的消息文件,并根据语言将它们合并。这意味着你可以自由组织你的消息文件。
例如,您可能希望采取传统的方法,在一个文件中定义所有的:
~~~
/app
/messages
messages.en
messages.fr
...
~~~
或者是为每种语言创建多个文件,并根据分类组织他们:
~~~
/app
/messages
labels.en
warnings.en
labels.fr
warnings.fr
...
~~~
**重要注意事项:** 在同一语言的多个文件中,虽然技术上可以定义相同的消息key,但是这将导致不可预知的行为。当每种语言定义了多个文件时,请注意保持您的key唯一,这样文件合并后,key将不会被覆盖!
### 消息 key 和 值
消息文件是一个 [goconfig 文件](https://github.com/revel/goconfig)。这意味着,消息应按照key-value格式定义:
~~~
key=value
~~~
举个栗子:
~~~
greeting=Hello
greeting.name=Rob
greeting.suffix=, welcome to Revel!
~~~
### 段
goconfig 文件被分成多个 *sections*. *默认的段* 总是存在, 并且包含未指定段时定义的消息。例如:
~~~
key=value
[SECTION]
key2=value2
~~~
`key=value` 消息消息被隐式放置在默认段中,因为它没有定义在一个指定的段中。
消息文件的所有消息应在定义默认的段中,除非它们是用于某个特定区域(见[Regions](http://gorevel.cn/docs/manual/i18n-messages.html#regions)获取更多消息)。
**注意:** 段是 *goconfig* 功能.
### 区域
特定区域的消息应当在相同的段中定义。Region-specific messages should be defined in sections with the same name. 例如,假设我们要对所有讲英语的用户显示 `"Hello"`, 对英国用户显示 `"Hey"` ,对讲美国用户显示 `"Howdy"`。 为了做到这一点,我们可以定义下面的消息文件 `greeting.en`:
~~~
greeting=Hello
[GB]
greeting=Hey
[US]
greeting=Howdy
~~~
对于定义了英语 (`en`) 作为他们的首选语言时,Revel 会将`greeting` 解析成 `Hello`。只有在用户的区域被明确定义的情况下,比如`en-GB` 或 `en-US` ,Revel 才会用对应段中的消息去解析 `greeting`。
**重要提示:** 如果定义了一个不合法的区域,技术上是允许的,但是它们永远不会被解析。
### 引用 和 参数
#### 引用
文件中的消息也可以引用其他消息。这使得用户可以从一个或多个其它的消息组成一个单一的消息。引用消息的语法是 `%(key)s`. 例如:
~~~
greeting=Hello
greeting.name=Rob
greeting.suffix=, welcome to Revel!
greeting.full=%(greeting)s %(greeting.name)s%(greeting.suffix)s
~~~
**注意:**
* 引用是 *goconfig* 的功能.
* 因为消息文件可以合并,多以只要它们被定义为同一种语言,就可以引用其他文件中的消息。
#### 参数
消息支持一个或多个参数。在消息中参数使用与 `fmt` 包相同的规则去解析。 例如:
~~~
greeting.name_arg=Hello %s!
~~~
参数按照给定的顺序进行解析,参考 [解析消息](http://gorevel.cn/docs/manual/i18n-messages.html#resolving_messages).
## 解析客户端语言环境(locale)
为了找出用户喜欢的语言环境,Revel会在以下地方查找一个可用的语言环境:
1. 语言 cookie
对于每次请求,框架会查找应用程序配置 (`i18n.cookie`)。如果找到了,它的值被假定为当前的语言环境。当一个cookie已经发现,所有其他解析方法将被跳过。
2. `Accept-Language` HTTP 头
对于每次请求,Revel 会自动解析 HTTP 头信息的 `Accept-Language`。其中的每个语言环境根据 [HTTP 规范](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)在当前`Request`实例中获取和存储。此信息用来为稍后的消息解析函数确定当前语言环境。
更多信息请参考 [Parsed Accept-Language HTTP header](http://gorevel.cn/docs/manual/i18n-messages.html#parsed_acceptlanguage_http_header).
3. 默认语言
当以上所有方法都没有找到可用的客户端语言环境时,框架将使用配置文件中定义的默认语言`i18n.default_language`。
如果有的消息解析失败,则返回一个包含原始消息名的特殊格式的字符串。
**注意:** `Accept-Language` 头信息 **总是** 被解析并保存到当前 `Request`中, 即使它已经存在。在这种情况下,头信息中的值即使从未被消息解析功能使用,但是仍可以在需要他们的时候使用。
### 检查当前语言环境
应用程序可以从`Request`内部使用 `Request.Locale` 属性访问当前语言环境。例如:
~~~
func (c App) Index() revel.Result {
currentLocale := c.Request.Locale
c.Render(currentLocale)
}
~~~
在模板中, 就可以从 `renderArgs` 中的 `currentLocale` 变量中访问当前语言环境。 例如:
~~~
<p>Current preferred locale: {{.currentLocale}}</p>
~~~
### 解析 Accept-Language HTTP 头信息
如果程序需要访问 `Accept-Language` HTTP 头信息, 可以从 `Controller` 实例的 `Request`中获取。 `AcceptLanguages` 字段(`AcceptLanguage`实例的切片)包含所有从各自的头信息中解析的值, 最合适的值是切片中的第一个。例如:
~~~
func (c App) Index() revel.Result {
// 获取所有解析到的AcceptLanguage的字符串表示形式
c.RenderArgs["acceptLanguageHeaderParsed"] = c.Request.AcceptLanguages.String()
// 返回最合适AcceptLanguage实例
c.RenderArgs["acceptLanguageHeaderMostQualified"] = c.Request.AcceptLanguages[0]
c.Render()
}
~~~
更多信息请参考 [HTTP 规范](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4).
## 解析消息
消息可以在 *控制器* 或 *模板*中解析。
### 控制器
控制器中使用 `Message(message string, args ...interface{})` 函数使用当前语言环境解析消息:
~~~
func (c App) Index() revel.Result {
c.RenderArgs["controllerGreeting"] = c.Message("greeting")
c.Render()
}
~~~
### 模板
在模板中使用 *模板函数* `msg` 解析当前语言环境的消息。例如:
~~~
<p>Greetings without arguments: {{msg . "greeting"}}</p>
<p>Greetings: {{msg . "greeting.full.name" "Tommy Lee Jones"}}</p>
~~~
**注意:** `msg` 函数的签名是 `msg . "message name" "argument" "argument"`. 如果没有参数, 可以忽略。
## 配置
| 文件 | 选项 | 描述 |
| --- | --- | --- |
| `app.conf` | `i18n.cookie` | 语言cookie的名称。应使用Revel cookie的前缀,以避免cookie名称冲突。 |
| `app.conf` | `i18n.default_language` | 默认的语言环境,在没有首选区域的情况下使用。 |
Websockets
最后更新于:2022-04-01 04:22:09
Revel 提供[Websockets](http://en.wikipedia.org/wiki/WebSocket)支持。
处理一个 Websocket 连接:
1. 添加一个 `WS` 类型的路由。
2. 添加一个接受 `*websocket.Conn` 参数的控制器方法.
举个栗子, 在 `routes` 文件中添加路由:
~~~
WS /app/feed Application.Feed
~~~
添加一个控制器方法,接受`*websocket.Conn` 参数:
~~~
import "code.google.com/p/go.net/websocket"
func (c App) Feed(user string, ws *websocket.Conn) revel.Result {
...
}
~~~
过滤器
最后更新于:2022-04-01 04:22:07
过滤器是Revel框架的中间件 – 是组成请求处理管道的独立的功能。他们执行框架的所有功能。
过滤器类型是一个简单的函数:
~~~
type Filter func(c *Controller, filterChain []Filter)
~~~
每个过滤器负责调用过滤器链中的下一个过滤器。下面是个默认的过滤器栈:
~~~
// Filters 是默认的全局过滤器集。
// 可以在程序初始化时设置它。
var Filters = []Filter{
PanicFilter, // 从恐慌中恢复,并显示一个错误页面。
RouterFilter, // 负责解析路由,并选择正确的控制器方法。
FilterConfiguringFilter, // 用于添加/删除每个动作过滤的钩子。
ParamsFilter, // 解析参数到 Controller.Params 中。
SessionFilter, // 恢复和写入会话 cookie。
FlashFilter, // 恢复和写入 flash cookie。
ValidationFilter, // 恢复保存验证错误并保存新的Cookie中。
I18nFilter, // 解析请求语言。
InterceptorFilter, // 执行拦截器。
ActionInvoker, // 调用控制器。
}
~~~
## 过滤器链配置
### 全局配置
程序可以在 `init()` 中重写 `revel.Filters` 变量,来配置过滤器链 (默认在 `app/init.go`)。
~~~
func init() {
// Filters 是默认的全局过滤器集。
revel.Filters = []Filter{
PanicFilter, // 从恐慌中恢复,并显示一个错误页面。
RouterFilter, // 负责解析路由,并选择正确的控制器方法。
FilterConfiguringFilter, // 用于添加/删除每个动作过滤的钩子。
ParamsFilter, // 解析参数到 Controller.Params 中。
SessionFilter, // 恢复和写入会话 cookie。
FlashFilter, // 恢复和写入 flash cookie。
ValidationFilter, // 恢复保存验证错误并保存新的Cookie中。
I18nFilter, // 解析请求语言。
InterceptorFilter, // 执行拦截器。
ActionInvoker, // 调用控制器。
}
}
~~~
每个请求沿着过滤器链从上到下依次执行。
### Per-Action configuration
尽管所有的请求都被发往过滤器链 `revel.Filters`, Revel 也提供了 [`过滤器配置`](http://gorevel.cn/docs/docs/godoc/filterconfig.html#FilterConfigurator), 允许开发者根据操作或控制器添加、插入、删除过滤器。
此功能通过 `FilterConfiguringFilter` 实现, 它本身就是一个过滤器.
## 实现一个过滤器
### 保持过滤器链能够依次执行
Filters 负责依次调用下一个过滤器来依次处理请求。这通常需要完成下面的表达式:
~~~
var MyFilter = func(c *revel.Controller, fc []revel.Filter) {
// .. 做一些预处理 ..
fc[0](c, fc[1:]) // 执行下一个过滤器
// .. 做一些后期处理 ..
}
~~~
### 获取控制器类型
Filters 接受一个 `*Controller` 类型的参数, 而不是被调用的实际的控制器类型。如果过滤器需要访问实际的控制器类型,可以这样实现:
~~~
var MyFilter = func(c *revel.Controller, fc []revel.Filter) {
if ac, err := c.AppController.(*MyController); err == nil {
// 判定存在一个 *MyController 实例...
}
fc[0](c, fc[1:]) // 执行下一个过滤器
}
~~~
注意:这种模式往往说明[拦截器](http://gorevel.cn/docs/manual/interceptors.html)可能是实现所需功能的好的机制的一个指标。
拦截器
最后更新于:2022-04-01 04:22:05
“拦截器”是框架执行一个方法之前或之后被调用的函数。它允许 [面向方面编程](http://en.wikipedia.org/wiki/Aspect-oriented_programming), 作用如下:
* 请求记录
* 错误处理
* 状态保持
在 Revel 中, 有两种形式的拦截器:
1. 函数拦截器:请参考 [`InterceptorFunc`](http://gorevel.cn/docs/docs/godoc/intercept.html#InterceptorFunc) 接口.
* 不能挂接到某个特定的控制器方法
* 可以应用到所有的、任意的控制器上
2. 方法拦截器:一个不带参数、并返回一个 `revel.Result`的控制器方法
* 只能拦截控制器方法
* 可以修改被调用的控制器
拦截器的执行顺序与添加位置相关
## 拦截时间
在一个请求生命周期内,可以注册四种拦截时间:
1. BEFORE: 在请求被路由到以后,并且session, flash, 参数解析之后、控制器方法被调用之前执行拦截。
2. AFTER: 在请求返回了一个结果, 但是结果被应用之前执行拦截。如果出现了panic,拦截不会被调用。
3. PANIC: 在控制器方法中或应用结果时出现panic退出后被拦截。
4. FINALLY: 在控制器方法执行完毕并且结果被应用之后被拦截。
## 结果
拦截器通常返回 `nil`, 在这种情况下,需要继续处理请求,不能中断。
返回一个非 `nil` `revel.Result`的效果, 取决于拦截器被调用的时间:
1. BEFORE: 没有进一步的拦截器被调用, 也不是一个控制器方法。
2. AFTER: 所有拦截器仍然可以运行。
3. PANIC: 所有拦截器仍然可以运行。
4. FINALLY: 所有拦截器仍然可以运行。
在任何情况下,返回的结果都将附加到任何现有的结果上:
BEFORE:返回的结果是保证是最终的。
AFTER:它可能是一个进一步的拦截器,可以返回自己的结果。
## 例如
### 函数拦截器
下面是定义和注册函数拦截器的一个简单例子。
~~~
func checkUser(c *revel.Controller) revel.Result {
if user := connected(c); user == nil {
c.Flash.Error("请先登录")
return c.Redirect(App.Index)
}
return nil
}
func init() {
revel.InterceptFunc(checkUser, revel.BEFORE, &Hotels{})
}
~~~
### 方法拦截器
方法拦截器有两种方式的签名:
~~~
func (c AppController) example() revel.Result
func (c *AppController) example() revel.Result
~~~
下面是个同样的例子,只能拦截一个控制器。
~~~
func (c Hotels) checkUser() revel.Result {
if user := connected(c); user == nil {
c.Flash.Error("请先登录")
return c.Redirect(App.Index)
}
return nil
}
func init() {
revel.InterceptMethod(Hotels.checkUser, revel.BEFORE)
}
~~~
模板
最后更新于:2022-04-01 04:22:02
Revel 使用 [Go 模板](http://www.golang.org/pkg/text/template/), 在下面两个目录中查找模板:
* 应用程序的 `views` 目录 (包括所有的子目录)
* Revel的 `templates` 目录.
比如有一个控制器 `Hello` ,方法名为 `World`, Revel 会查找名字为 `views/Hello/World.html`的模板。模板名字不区分大小写,所以 `views/hello/world.html` 与 `views/HeLlO/wOrLd.HtMl`都是匹配的模板.
Revel 提供了错误页面模板 (在开发模式中友好的显示编译错误), 开发者也可以重写这些模板,比如`app/views/errors/500.html`.
## 渲染上下文
Revel 使用 RenderArgs map 渲染模板。除了开发者传送的数据, Revel 也提供一些有用的数据:
* “errors” - 验证错误(map,请参考文档 [`Validation.ErrorMap`](http://gorevel.cn/docs/docs/godoc/validation.html#Validation.ErrorMap)
* “flash” - 上个请求flash的数据
## 模板功能
Go 提供了一些 [模板函数](http://www.golang.org/pkg/text/template/#Functions)。Revel 也增加了一些模板函数。请阅读下面的文档 或 [查看源代码](http://gorevel.cn/docs/docs/godoc/template.html#variables).
### eq
一个简单的 “a == b” 测试.
例如:
~~~
<div class="message {{if eq .User "you"}}you{{end}}">
~~~
### set
在当前模板上下文中设置一个变量
例如:
~~~
{{set . "title" "Basic Chat room"}}
<h1>{{.title}}</h1>
~~~
### append
添加变量到一个数组中, 或者在模板上下文中创建一个数组
例如:
~~~
{{append . "moreScripts" "js/jquery-ui-1.7.2.custom.min.js"}}
{{range .moreStyles}}
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
{{end}}
~~~
### field
input 字段辅助函数.
给出一个字段名, 函数会生成包含下面成员的结构:
* Id: 字段Id, 转换为一个HTML字段的ID。
* Name: 字段名称
* Value: 当前上下文中字段的值
* Flash: 带回的字段值
* Error: 字段错误消息(如果有错误)
* ErrorClass: 原始的字符串 “hasError”, 如果没有错误就是一个 ””.
[浏览 godoc.](http://gorevel.cn/docs/docs/godoc/field.html)
例如:
~~~
{{with $field := field "booking.CheckInDate" .}}
<p class="{{$field.ErrorClass}}">
<strong>Check In Date:</strong>
<input type="text" size="10" name="{{$field.Name}}" class="datepicker" value="{{$field.Flash}}">
* <span class="error">{{$field.Error}}</span>
</p>
{{end}}
~~~
### option
使用辅助函数生成 HTML `option` 字段。
例如:
~~~
{{with $field := field "booking.Beds" .}}
<select name="{{$field.Name}}">
{{option $field "1" "One king-size bed"}}
{{option $field "2" "Two double beds"}}
{{option $field "3" "Three beds"}}
</select>
{{end}}
~~~
### radio
使用辅助函数生成 HTML radio `input` 字段
例如:
~~~
{{with $field := field "booking.Smoking" .}}
{{radio $field "true"}} Smoking
{{radio $field "false"}} Non smoking
{{end}}
~~~
### nl2br
将换行符转换成 HTML 的 break.
例如:
~~~
You said:
<div class="comment">{{nl2br .commentText}}</div>
~~~
### pluralize
一个辅助的复数函数
例如:
~~~
There are {{.numComments}} comment{{pluralize (len comments) "" "s"}}
~~~
### raw
输出原生的、未转义的文本
例如:
~~~
<div class="body">{{raw .blogBody}}</div>
~~~
## Including
Go 模板允许你在模板中包含其他模板,比如:
~~~
{{template "header.html" .}}
~~~
注意: * 相对路径是 `app/views`
## 温馨提示
Revel 应用程序有效利用 Go 模板,请看看下面的例子:
* `revel/samples/booking/app/views/header.html`
* `revel/samples/booking/app/views/Hotels/Book.html`
使用辅助函数,为模板设置标题和额外的样式。
例如:
~~~
<html>
<head>
<title>{{.title}}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" type="text/css" media="screen" href="/public/css/main.css">
<link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
{{range .moreStyles}}
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
{{end}}
<script src="/public/js/jquery-1.3.2.min.js" type="text/javascript" charset="utf-8"></script>
<script src="/public/js/sessvars.js" type="text/javascript" charset="utf-8"></script>
{{range .moreScripts}}
<script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
{{end}}
</head>
~~~
在模板中这样使用:
~~~
{{set . title "Hotels"}}
{{append . "moreStyles" "ui-lightness/jquery-ui-1.7.2.custom.css"}}
{{append . "moreScripts" "js/jquery-ui-1.7.2.custom.min.js"}}
{{template "header.html" .}}
~~~
## 自定义模板函数
应用程序可以注册自定义模板函数
例如:
~~~
func init() {
revel.TemplateFuncs["eq"] = func(a, b interface{}) bool { return a == b }
}
~~~
Results
最后更新于:2022-04-01 04:22:00
控制器方法必须返回一个[`revel.Result`](http://gorevel.cn/docs/docs/godoc/results.html#Result), 用来处理响应结果,其接口定义如下:
~~~
type Result interface {
Apply(req *Request, resp *Response)
}
~~~
[`revel.Controller`](http://gorevel.cn/docs/docs/godoc/controller.html#Controller) 使用以下方法来处理响应结果:
* Render, RenderTemplate - 渲染模板, 传送参数.
* RenderJson, RenderXml - 序列化结构体到 json 或 xml.
* RenderText - 返回一个明文响应.
* Redirect - 重定向到一个控制器方法 或 URL
* RenderFile - 返回一个文件, 一般用于响应文件下载.
* RenderError - 渲染 errors/500.html 模板来返回一个 500 错误.
* NotFound - 渲染 errors/404.html 模板来返回一个 404 错误.
* Todo - 返回一个存根响应(500)
此外,开发者可以自定义revel.Result.
### 设置状态码/内容类型
每个内建的 Result 都有一个默认的状态码和内容类型,简单的设置相关属性就可以修改这些默认值:
~~~
func (c App) Action() revel.Result {
c.Response.Status = http.StatusTeapot
c.Response.ContentType = "application/dishware"
return c.Render()
}
~~~
## 渲染
控制器渲染方法 (e.g. “Controller.Action”), [`mvc.Controller.Render`](http://gorevel.cn/docs/docs/godoc/controller.html#Controller.Render) 做了两件事:
1. 添加参数到控制器的 RenderArgs 中, 使用变量的名字作为字典的key.
2. 使用传送的参数渲染模板 “views/Controller/Action.html”。
如果不成功 (比如,没有找到模板), 将返回 ErrorResult.
这允许开发人员编写:
~~~
func (c MyApp) Action() revel.Result {
myValue := calculateValue()
return c.Render(myValue)
}
~~~
在模板中引用变量 “myValue”。这通常比构建一个明确的map更方便,因为在许多情况下,数据将被作为局部变量处理。
**注意:** Revel 通过调用的控制器方法名来确定使用哪个模板,查找参数名。因此, c.Render() 只可称为由操作。
## RenderJson / RenderXml
应用程序可以调用 [`RenderJson`](http://gorevel.cn/docs/docs/godoc/controller.html#Controller.RenderJson) 或 [`RenderXml`](http://gorevel.cn/docs/docs/godoc/controller.html#Controller.RenderXml) 并传送任意类型的变量 (通常是一个 struct). Revel 会使用 [`json.Marshal`](http://www.golang.org/pkg/encoding/json/#Marshal) or[`xml.Marshal`](http://www.golang.org/pkg/encoding/xml/#Marshal)对变量进行序列化操作.
如果 `app.conf` 配置文件中 `results.pretty=true`, 将使用 `MarshalIndent` 进行序列化, 生成漂亮的缩进,方便使用。
## 重定向
一个辅助函数是用于生成重定向。它有两种方式使用:
1. 重定向到一个控制器方法(不带参数):
~~~
return c.Redirect(Hotels.Settings)
~~~
这种形式是非常有用的,因为它提供了一定程度的路由类型安全性和独立性(自动生成URL)。
1. 重定向到一个格式化字符串:
~~~
return c.Redirect("/hotels/%d/settings", hotelId)
~~~
通常用来传送参数.
它返回 302 (临时重定向) 状态码.
## 添加你自己的 Result
下面是一个添加简单结果的例子。
创建此类型:
~~~
type Html string
func (r Html) Apply(req *Request, resp *Response) {
resp.WriteHeader(http.StatusOK, "text/html")
resp.Out.Write([]byte(r))
}
~~~
在一个控制器方法中使用它:
~~~
func (c *App) Action() revel.Result {
return Html("<html><body>Hello World</body></html>")
}
~~~
## 状态码
每一个Result 都会设置一个默认的状态码,你也可以重新设置默认的状态代码:
~~~
func (c *App) CreateEntity() revel.Result {
c.Response.Status = 201
return c.Render()
}
~~~
Session / Flash
最后更新于:2022-04-01 04:21:58
Revel 支持两种 基于 cookie 存储机制
~~~
// 一个签名 cookie (不超过4kb).
// 限制: Keys may not have a colon in them.
type Session map[string]string
// 在每个请求中,Flash 获取并重写cookie。
// 它允许数据每次跨越存储到一个页面。It allows data to be stored across one page at a time.
// 它通常用来实现成功或错误消息。
// 比如: Post/Redirect/Get : http://en.wikipedia.org/wiki/Post/Redirect/Get
type Flash struct {
Data, Out map[string]string
}
~~~
## Session
Revel的 “session” 是一个加密签名存储的字符串 map。
影响如下:
* 大小不超过 4kb。
* 所有数据被保存为一个序列化的字符串。
* 用户可以查看、修改所有数据 (未加密)。
session cookie 的默认过期时间是浏览器关闭。 可以在app.config修改session.expires配置来指定一个有效期时间。格式是[time.ParseDuration](http://golang.org/pkg/time/#ParseDuration).
## Flash
Flash 支持单独使用字符串存储。这对于实现 [Post/Redirect/Get 模式](http://en.wikipedia.org/wiki/Post/Redirect/Get)是很有用的, 或临时显示 “操作成功!” 或 “操作失败!” 消息。
下面是一个例子:
~~~
// 一个设置页面
func (c App) ShowSettings() revel.Result {
return c.Render()
}
// 处理页面提交数据
func (c App) SaveSettings(setting string) revel.Result {
// 验证用户输入
c.Validation.Required(setting)
if c.Validation.HasErrors() {
// 设置被带回的flash cookie错误信息
c.Flash.Error("Settings invalid!")
// 在flash cookie中保存验证错误
c.Validation.Keep()
// 复制所有给定的参数(URL, Form, Multipart)到flash cookie
c.FlashParams()
return c.Redirect(App.ShowSettings)
}
saveSetting(setting)
// 设置flash cookie成功消息
c.Flash.Success("Settings saved!")
return c.Redirect(App.ShowSettings)
}
~~~
例子主要功能如下:
1. 用户获取设置页面。
2. 用户提交一个表单 (POST)。
3. 应用程序处理用户请求,保存错误或成功的消息到flash中,然后重定向设置页面 (REDIRECT)。
4. 用户获取设置页面, 显示flash中的消息。 (GET)
主要使用了两个函数:
1. `Flash.Success(message string)` 是 `Flash.Out["success"] = message`简写方式
2. `Flash.Error(message string)` 是 `Flash.Out["error"] = message`简写方式
在模板中使用key来获取Flash 消息。例如, 通过简写函数获取成功和错误消息:
~~~
{{.flash.success}}
{{.flash.error}}
~~~