本文讲述 beego 中使用的模板语法,与 go 模板语法基本相同。
go 统一使用了 {{
和 }}
作为左右标签,没有其他的标签符号。如果您想要修改为其它符号,可以参考 模板标签。
使用 .
来访问当前位置的上下文
使用 $
来引用当前模板根级的上下文
使用 $var
来访问创建的变量
[more]
模板中支持的 go 语言符号
{{"string"}} // 一般 string
{{`raw string`}} // 原始 string
{{'c'}} // byte
{{print nil}} // nil 也被支持
模板中的 pipeline
可以是上下文的变量输出,也可以是函数通过管道传递的返回值
{{. | FuncA | FuncB | FuncC}}
当 pipeline 的值等于:
那么这个 pipeline 被认为是空
{{if pipeline}}{{end}}
if 判断时,pipeline 为空时,相当于判断为 False
this.Data["IsLogin"] = true
this.Data["IsHome"] = true
this.Data["IsAbout"] = true
支持嵌套的循环
{{if .IsHome}}
{{else}}
{{if .IsAbout}}{{end}}
{{end}}
也可以使用 else if 进行
{{if .IsHome}}
{{else if .IsAbout}}
{{else}}
{{end}}
{{range pipeline}}{{.}}{{end}}
pipeline 支持的类型为 array, slice, map, channel
range 循环内部的 .
改变为以上类型的子元素
对应的值长度为 0 时,range 不会执行,.
不会改变
pages := []struct {
Num int
}{{10}, {20}, {30}}
this.Data["Total"] = 100
this.Data["Pages"] = pages
使用 .Num
输出子元素的 Num 属性,使用 $.
引用模板中的根级上下文
{{range .Pages}}
{{.Num}} of {{$.Total}}
{{end}}
使用创建的变量,在这里和 go 中的 range 用法是相同的。
{{range $index, $elem := .Pages}}
{{$index}} - {{$elem.Num}} - {{.Num}} of {{$.Total}}
{{end}}
range 也支持 else
{{range .Pages}}
{{else}}
{{/* 当 .Pages 为空 或者 长度为 0 时会执行这里 */}}
{{end}}
{{with pipeline}}{{end}}
with 用于重定向 pipeline
{{with .Field.NestField.SubField}}
{{.Var}}
{{end}}
也可以对变量赋值操作
{{with $value := "My name is %s"}}
{{printf . "slene"}}
{{end}}
with 也支持 else
{{with pipeline}}
{{else}}
{{/* 当 pipeline 为空时会执行这里 */}}
{{end}}
define 可以用来定义自模板,可用于模块定义和模板嵌套
{{define "loop"}}
<li>{{.Name}}</li>
{{end}}
使用 template 调用模板
<ul>
{{range .Items}}
{{template "loop" .}}
{{end}}
</ul>
{{template "模板名" pipeline}}
将对应的上下文 pipeline 传给模板,才可以在模板中调用
Beego 中支持直接载入文件模板
{{template "path/to/head.html" .}}
Beego 会依据你设置的模板路径读取 head.html
在模板中可以接着载入其他模板,对于模板的分模块处理很有用处
允许多行文本注释,不允许嵌套
{{/* comment content
support new line */}}
变量可以使用符号 |
在函数间传递
{{.Con | markdown | addlinks}}
{{.Name | printf "%s"}}
使用括号
{{printf "nums is %s %d" (printf "%d %d" 1 2) 3}}
{{and .X .Y .Z}}
and 会逐一判断每个参数,将返回第一个为空的参数,否则就返回最后一个非空参数
{{call .Field.Func .Arg1 .Arg2}}
call 可以调用函数,并传入参数
调用的函数需要返回 1 个值 或者 2 个值,返回两个值时,第二个值用于返回 error 类型的错误。返回的错误不等于 nil 时,执行将终止。
index 支持 map, slice, array, string,读取指定类型对应下标的值
this.Data["Maps"] = map[string]string{"name": "Beego"}
{{index .Maps "name"}}
{{printf "The content length is %d" (.Content|len)}}
返回对应类型的长度,支持类型:map, slice, array, string, chan
not 返回输入参数的否定值,if true then false else true
{{or .X .Y .Z}}
or 会逐一判断每个参数,将返回第一个非空的参数,否则就返回最后一个参数
对应 fmt.Sprint
对应 fmt.Sprintf
对应 fmt.Sprintln
{{urlquery "http://beego.vip"}}
将返回
http%3A%2F%2Fbeego.vip
这类函数一般配合在 if 中使用
eq
: arg1 == arg2
ne
: arg1 != arg2
lt
: arg1 < arg2
le
: arg1 <= arg2
gt
: arg1 > arg2
ge
: arg1 >= arg2
eq 和其他函数不一样的地方是,支持多个参数,和下面的逻辑判断相同
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
与 if 一起使用
{{if eq true .Var1 .Var2 .Var3}}{{end}}
{{if lt 100 200}}{{end}}
beego 的模板处理引擎采用的是 Go 内置的 html/template
包进行处理,而且 beego 的模板处理逻辑是采用了缓存编译方式,也就是所有的模板会在 beego 应用启动的时候全部编译然后缓存在 map 里面。
beego 中默认的模板目录是 views
,用户可以把模板文件放到该目录下,beego 会自动在该目录下的所有模板文件进行解析并缓存,开发模式下每次都会重新解析,不做缓存。当然,用户也可以通过如下的方式改变模板的目录(只能指定一个目录为模板目录):
beego.ViewsPath = "myviewpath"
用户无需手动的调用渲染输出模板,beego 会自动的在调用完相应的 method 方法之后调用 Render 函数,当然如果您的应用是不需要模板输出的,那么可以在配置文件或者在 main.go
中设置关闭自动渲染。
配置文件配置如下:
autorender = false
main.go 文件中设置如下:
web.AutoRender = false
Go 语言的默认模板采用了 {{
和 }}
作为左右标签,但是我们有时候在开发中可能界面是采用了 AngularJS 开发,他的模板也是这个标签,故而引起了冲突。在 beego 中你可以通过配置文件或者直接设置配置变量修改:
web.TemplateLeft = "<<<"
web.TemplateRight = ">>>"
模板中的数据是通过在 Controller 中 this.Data
获取的,所以如果你想在模板中获取内容 {{.Content}}
,那么你需要在 Controller 中如下设置:
this.Data["Content"] = "value"
如何使用各种类型的数据渲染:
结构体
结构体结构
go type A struct{ Name string Age int }
控制器数据赋值
this.Data["a"]=&A{Name:"astaxie",Age:25}
模板渲染数据如下:
the username is {{.a.Name}}
the age is {{.a.Age}}
map
控制器数据赋值
mp["name"]="astaxie"
mp["nickname"] = "haha"
this.Data["m"]=mp
模板渲染数据如下:
the username is {{.m.name}}
the username is {{.m.nickname}}
slice
控制器数据赋值
ss :=[]string{"a","b","c"}
this.Data["s"]=ss
模板渲染数据如下:
{{range $key, $val := .s}}
{{$key}}
{{$val}}
{{end}}
beego 采用了 Go 语言内置的模板引擎,所有模板的语法和 Go 的一模一样,至于如何写模板文件,详细的请参考 模板教程。
用户通过在 Controller 的对应方法中设置相应的模板名称,beego 会自动的在 viewpath 目录下查询该文件并渲染,例如下面的设置,beego 会在 admin 下面找 add.tpl 文件进行渲染:
this.TplName = "admin/add.tpl"
我们看到上面的模板后缀名是 tpl,beego 默认情况下支持 tpl 和 html 后缀名的模板文件,如果你的后缀名不是这两种,请进行如下设置:
web.AddTemplateExt("你文件的后缀名")
当你设置了自动渲染,然后在你的 Controller 中没有设置任何的 TplName,那么 beego 会自动设置你的模板文件如下:
c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
也就是你对应的 Controller 名字+请求方法名.模板后缀,也就是如果你的 Controller 名是 AddController
,请求方法是 POST
,默认的文件后缀是 tpl
,那么就会默认请求 /viewpath/AddController/post.tpl
文件。
beego 支持 layout 设计,例如你在管理系统中,整个管理界面是固定的,只会变化中间的部分,那么你可以通过如下的设置:
this.Layout = "admin/layout.html"
this.TplName = "admin/add.tpl"
在 layout.html 中你必须设置如下的变量:
{{.LayoutContent}}
beego 就会首先解析 TplName 指定的文件,获取内容赋值给 LayoutContent,然后最后渲染 layout.html 文件。
目前采用首先把目录下所有的文件进行缓存,所以用户还可以通过类似这样的方式实现 layout:
{{template "header.html" .}}
Logic code
{{template "footer.html" .}}
特别注意后面的
.
,这是传递当前参数到子模板
对于一个复杂的 LayoutContent
,其中可能包括有javascript脚本、CSS 引用等,根据惯例,通常 css 会放到 Head 元素中,javascript 脚本需要放到 body 元素的末尾,而其它内容则根据需要放在合适的位置。在 Layout
页中仅有一个 LayoutContent
是不够的。所以在 Controller
中增加了一个 LayoutSections
属性,可以允许 Layout
页中设置多个 section
,然后每个 section
可以分别包含各自的子模板页。
layout_blog.tpl:
<!DOCTYPE html>
<html>
<head>
<title>Lin Li</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css">
{{.HtmlHead}}
</head>
<body>
<div class="container">
{{.LayoutContent}}
</div>
<div>
{{.SideBar}}
</div>
<script type="text/javascript" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
{{.Scripts}}
</body>
</html>
html_head.tpl:
<style>
h1 {
color: red;
}
</style>
scripts.tpl:
<script type="text/javascript">
$(document).ready(function() {
// bla bla bla
});
</script>
逻辑处理如下所示:
type BlogsController struct {
web.Controller
}
func (this *BlogsController) Get() {
this.Layout = "layout_blog.tpl"
this.TplName = "blogs/index.tpl"
this.LayoutSections = make(map[string]string)
this.LayoutSections["HtmlHead"] = "blogs/html_head.tpl"
this.LayoutSections["Scripts"] = "blogs/scripts.tpl"
this.LayoutSections["Sidebar"] = ""
}
定义 struct:
```go
type User struct {
Id int `form:"-"`
Name interface{} `form:"username"`
Age int `form:"age,text,年龄:"`
Sex string
Intro string `form:",textarea"`
}
```
form
,和 ParseForm 方法 共用一个标签,标签后面有三个可选参数,用 ,
分割。第一个参数为表单中类型的 name
的值,如果为空,则以 struct field name
为值。第二个参数为表单组件的类型,如果为空,则为 text
。表单组件的标签默认为 struct field name
的值,否则为第三个值。form
标签只有一个值,则为表单中类型 name
的值,除了最后一个值可以忽略外,其他位置的必须要有 ,
号分割,如:form:",,姓名:"
form
标签的值设置为 -
controller:
```go
func (this *AddController) Get() {
this.Data["Form"] = &User{}
this.TplName = "index.tpl"
}
```
Form 的参数必须是一个 struct 的指针。
template:
<form action="" method="post">
{{.Form | renderform}}
</form>
上面的代码生成的表单为:
Name: <input name="username" type="text" value="test"></br>
年龄:<input name="age" type="text" value="0"></br>
Sex: <input name="Sex" type="text" value=""></br>
Intro: <input name="Intro" type="textarea" value="">
beego 支持用户定义模板函数,但是必须在 web.Run()
调用之前,设置如下:
```go
func hello(in string)(out string){
out = in + "world"
return
}
web.AddFuncMap(“hi”,hello)
```
定义之后你就可以在模板中这样使用了:
{{.Content | hi}}
目前 beego 内置的模板函数如下所示:
dateformat
实现了时间的格式化,返回字符串,使用方法 {{dateformat .Time “2006-01-02T15:04:05Z07:00”}}。
date
实现了类似 PHP 的 date 函数,可以很方便的根据字符串返回时间,使用方法 {{date .T “Y-m-d H:i:s”}}。
compare
实现了比较两个对象的比较,如果相同返回 true,否者 false,使用方法 {{compare .A .B}}。
substr
实现了字符串的截取,支持中文截取的完美截取,使用方法 {{substr .Str 0 30}}。
html2str
实现了把 html 转化为字符串,剔除一些 script、css 之类的元素,返回纯文本信息,使用方法 {{html2str .Htmlinfo}}。
str2html
实现了把相应的字符串当作 HTML 来输出,不转义,使用方法 {{str2html .Strhtml}}。
htmlquote
实现了基本的 html 字符转义,使用方法 {{htmlquote .quote}}。
htmlunquote
实现了基本的反转移字符,使用方法 {{htmlunquote .unquote}}。
renderform
根据 StructTag 直接生成对应的表单,使用方法 {{&struct | renderform}}。
assets_js
为 js 文件生成一个 <script>
标签. 使用方法 {{assets_js src}}
assets_css
为 css 文件生成一个 <link>
标签. 使用方法 {{assets_css src}}
config
获取 AppConfig 的值. 使用方法 {{config configType configKey defaultValue}}. 可选的 configType 有 String, Bool, Int, Int64, Float, DIY
map_get
获取 map
的值
用法:
// In controller
Data["m"] = map[string]interface{} {
"a": 1,
"1": map[string]float64{
"c": 4,
},
}
// In view
{{ map_get .m "a" }} // return 1
{{ map_get .m 1 "c" }} // return 4
urlfor
获取控制器方法的 URL
{{urlfor "TestController.List"}}
Go 语言内部其实已经提供了 http.ServeFile
,通过这个函数可以实现静态文件的服务。beego 针对这个功能进行了一层封装,通过下面的方式进行静态文件注册:
go web.SetStaticPath("/static","public")
beego 支持多个目录的静态文件注册,用户可以注册如下的静态文件目录:
go web.SetStaticPath("/images","images") web.SetStaticPath("/css","css") web.SetStaticPath("/js","js")
设置了如上的静态目录之后,用户访问 /images/login/login.png
,那么就会访问应用对应的目录下面的 images/login/login.png
文件。如果是访问 /static/img/logo.png
,那么就访问 public/img/logo.png
文件。
默认情况下 beego 会判断目录下文件是否存在,不存在直接返回 404 页面,如果请求的是 index.html
,那么由于 http.ServeFile
默认是会跳转的,不提供该页面的显示。因此 beego 可以设置 web.BConfig.WebConfig.DirectoryIndex=true
这样来使得显示 index.html
页面。而且开启该功能之后,用户访问目录就会显示该目录下所有的文件列表。
这里所说的分页,指的是大量数据显示时,每页显示固定的数量的数据,同时显示多个分页链接,用户点击翻页链接或页码时进入到对应的网页。
分页算法中需要处理的问题:
模板处理过程中经常需要分页,那么如何进行有效的开发和操作呢?
示例:
工具类
wetalk/modules/utils/paginator.go
package utils
import (
"math"
"net/http"
"net/url"
"strconv"
)
type Paginator struct {
Request *http.Request
PerPageNums int
MaxPages int
nums int64
pageRange []int
pageNums int
page int
}
func (p *Paginator) PageNums() int {
if p.pageNums != 0 {
return p.pageNums
}
pageNums := math.Ceil(float64(p.nums) / float64(p.PerPageNums))
if p.MaxPages > 0 {
pageNums = math.Min(pageNums, float64(p.MaxPages))
}
p.pageNums = int(pageNums)
return p.pageNums
}
func (p *Paginator) Nums() int64 {
return p.nums
}
func (p *Paginator) SetNums(nums interface{}) {
p.nums, _ = ToInt64(nums)
}
func (p *Paginator) Page() int {
if p.page != 0 {
return p.page
}
if p.Request.Form == nil {
p.Request.ParseForm()
}
p.page, _ = strconv.Atoi(p.Request.Form.Get("p"))
if p.page > p.PageNums() {
p.page = p.PageNums()
}
if p.page <= 0 {
p.page = 1
}
return p.page
}
func (p *Paginator) Pages() []int {
if p.pageRange == nil && p.nums > 0 {
var pages []int
pageNums := p.PageNums()
page := p.Page()
switch {
case page >= pageNums-4 && pageNums > 9:
start := pageNums - 9 + 1
pages = make([]int, 9)
for i, _ := range pages {
pages[i] = start + i
}
case page >= 5 && pageNums > 9:
start := page - 5 + 1
pages = make([]int, int(math.Min(9, float64(page+4+1))))
for i, _ := range pages {
pages[i] = start + i
}
default:
pages = make([]int, int(math.Min(9, float64(pageNums))))
for i, _ := range pages {
pages[i] = i + 1
}
}
p.pageRange = pages
}
return p.pageRange
}
func (p *Paginator) PageLink(page int) string {
link, _ := url.ParseRequestURI(p.Request.RequestURI)
values := link.Query()
if page == 1 {
values.Del("p")
} else {
values.Set("p", strconv.Itoa(page))
}
link.RawQuery = values.Encode()
return link.String()
}
func (p *Paginator) PageLinkPrev() (link string) {
if p.HasPrev() {
link = p.PageLink(p.Page() - 1)
}
return
}
func (p *Paginator) PageLinkNext() (link string) {
if p.HasNext() {
link = p.PageLink(p.Page() + 1)
}
return
}
func (p *Paginator) PageLinkFirst() (link string) {
return p.PageLink(1)
}
func (p *Paginator) PageLinkLast() (link string) {
return p.PageLink(p.PageNums())
}
func (p *Paginator) HasPrev() bool {
return p.Page() > 1
}
func (p *Paginator) HasNext() bool {
return p.Page() < p.PageNums()
}
func (p *Paginator) IsActive(page int) bool {
return p.Page() == page
}
func (p *Paginator) Offset() int {
return (p.Page() - 1) * p.PerPageNums
}
func (p *Paginator) HasPages() bool {
return p.PageNums() > 1
}
func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator {
p := Paginator{}
p.Request = req
if per <= 0 {
per = 10
}
p.PerPageNums = per
p.SetNums(nums)
return &p
}
模板
wetalk/views/base/paginator.html
{{if gt .paginator.PageNums 1}}
<ul class="pagination pagination-sm">
{{if .paginator.HasPrev}}
<li><a href="{{.paginator.PageLinkFirst}}">{{i18n .Lang "paginator.first_page"}}</a></li>
<li><a href="{{.paginator.PageLinkPrev}}"><</a></li>
{{else}}
<li class="disabled"><a>{{i18n .Lang "paginator.first_page"}}</a></li>
<li class="disabled"><a><</a></li>
{{end}}
{{range $index, $page := .paginator.Pages}}
<li{{if $.paginator.IsActive .}} class="active"{{end}}>
<a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
</li>
{{end}}
{{if .paginator.HasNext}}
<li><a href="{{.paginator.PageLinkNext}}">></a></li>
<li><a href="{{.paginator.PageLinkLast}}">{{i18n .Lang "paginator.last_page"}}</a></li>
{{else}}
<li class="disabled"><a>></a></li>
<li class="disabled"><a>{{i18n .Lang "paginator.last_page"}}</a></li>
{{end}}
</ul>
{{end}}
使用方法
package base
import (
"fmt"
"html/template"
"net/url"
"reflect"
"strings"
"time"
"github.com/astaxie/beego"
"github.com/astaxie/beego/validation"
"github.com/beego/i18n"
"github.com/beego/wetalk/modules/auth"
"github.com/beego/wetalk/modules/models"
"github.com/beego/wetalk/modules/utils"
"github.com/beego/wetalk/setting"
)
type NestPreparer interface {
NestPrepare()
}
// baseRouter implemented global settings for all other routers.
type BaseRouter struct {
beego.Controller
i18n.Locale
User models.User
IsLogin bool
}
// Prepare implemented Prepare method for baseRouter.
func (this *BaseRouter) Prepare() {
if setting.EnforceRedirect {
// if the host not matching app settings then redirect to AppUrl
if this.Ctx.Request.Host != setting.AppHost {
this.Redirect(setting.AppUrl, 302)
return
}
}
// page start time
this.Data["PageStartTime"] = time.Now()
// start session
this.StartSession()
// check flash redirect, if match url then end, else for redirect return
if match, redir := this.CheckFlashRedirect(this.Ctx.Request.RequestURI); redir {
return
} else if match {
this.EndFlashRedirect()
}
switch {
// save logined user if exist in session
case auth.GetUserFromSession(&this.User, this.CruSession):
this.IsLogin = true
// save logined user if exist in remember cookie
case auth.LoginUserFromRememberCookie(&this.User, this.Ctx):
this.IsLogin = true
}
if this.IsLogin {
this.IsLogin = true
this.Data["User"] = &this.User
this.Data["IsLogin"] = this.IsLogin
// if user forbided then do logout
if this.User.IsForbid {
auth.LogoutUser(this.Ctx)
this.FlashRedirect("/login", 302, "UserForbid")
return
}
}
// Setting properties.
this.Data["AppName"] = setting.AppName
this.Data["AppVer"] = setting.AppVer
this.Data["AppUrl"] = setting.AppUrl
this.Data["AppLogo"] = setting.AppLogo
this.Data["AvatarURL"] = setting.AvatarURL
this.Data["IsProMode"] = setting.IsProMode
this.Data["SearchEnabled"] = setting.SearchEnabled
this.Data["NativeSearch"] = setting.NativeSearch
this.Data["SphinxEnabled"] = setting.SphinxEnabled
// Redirect to make URL clean.
if this.setLang() {
i := strings.Index(this.Ctx.Request.RequestURI, "?")
this.Redirect(this.Ctx.Request.RequestURI[:i], 302)
return
}
// read flash message
beego.ReadFromRequest(&this.Controller)
// pass xsrf helper to template context
xsrfToken := this.Controller.XSRFToken()
this.Data["xsrf_token"] = xsrfToken
this.Data["xsrf_html"] = template.HTML(this.Controller.XSRFFormHTML())
// if method is GET then auto create a form once token
if this.Ctx.Request.Method == "GET" {
this.FormOnceCreate()
}
if app, ok := this.AppController.(NestPreparer); ok {
app.NestPrepare()
}
}
// on router finished
func (this *BaseRouter) Finish() {
}
func (this *BaseRouter) LoginUser(user *models.User, remember bool) string {
loginRedirect := strings.TrimSpace(this.Ctx.GetCookie("login_to"))
if utils.IsMatchHost(loginRedirect) == false {
loginRedirect = "/"
} else {
this.Ctx.SetCookie("login_to", "", -1, "/")
}
// login user
auth.LoginUser(user, this.Ctx, remember)
this.setLangCookie(i18n.GetLangByIndex(user.Lang))
return loginRedirect
}
// check if user not active then redirect
func (this *BaseRouter) CheckActiveRedirect(args ...interface{}) bool {
var redirect_to string
code := 302
needActive := true
for _, arg := range args {
switch v := arg.(type) {
case bool:
needActive = v
case string:
// custom redirect url
redirect_to = v
case int:
code = v
}
}
if needActive {
// check login
if this.CheckLoginRedirect() {
return true
}
// redirect to active page
if !this.User.IsActive {
this.FlashRedirect("/settings/profile", code, "NeedActive")
return true
}
} else {
// no need active
if this.User.IsActive {
if redirect_to == "" {
redirect_to = "/"
}
this.Redirect(redirect_to, code)
return true
}
}
return false
}
// check if not login then redirect
func (this *BaseRouter) CheckLoginRedirect(args ...interface{}) bool {
var redirect_to string
code := 302
needLogin := true
for _, arg := range args {
switch v := arg.(type) {
case bool:
needLogin = v
case string:
// custom redirect url
redirect_to = v
case int:
// custom redirect url
code = v
}
}
// if need login then redirect
if needLogin && !this.IsLogin {
if len(redirect_to) == 0 {
req := this.Ctx.Request
scheme := "http"
if req.TLS != nil {
scheme += "s"
}
redirect_to = fmt.Sprintf("%s://%s%s", scheme, req.Host, req.RequestURI)
}
redirect_to = "/login?to=" + url.QueryEscape(redirect_to)
this.Redirect(redirect_to, code)
return true
}
// if not need login then redirect
if !needLogin && this.IsLogin {
if len(redirect_to) == 0 {
redirect_to = "/"
}
this.Redirect(redirect_to, code)
return true
}
return false
}
// read beego flash message
func (this *BaseRouter) FlashRead(key string) (string, bool) {
if data, ok := this.Data["flash"].(map[string]string); ok {
value, ok := data[key]
return value, ok
}
return "", false
}
// write beego flash message
func (this *BaseRouter) FlashWrite(key string, value string) {
flash := beego.NewFlash()
flash.Data[key] = value
flash.Store(&this.Controller)
}
// check flash redirect, ensure browser redirect to uri and display flash message.
func (this *BaseRouter) CheckFlashRedirect(value string) (match bool, redirect bool) {
v := this.GetSession("on_redirect")
if params, ok := v.([]interface{}); ok {
if len(params) != 5 {
this.EndFlashRedirect()
goto end
}
uri := utils.ToStr(params[0])
code := 302
if c, ok := params[1].(int); ok {
if c/100 == 3 {
code = c
}
}
flag := utils.ToStr(params[2])
flagVal := utils.ToStr(params[3])
times := 0
if v, ok := params[4].(int); ok {
times = v
}
times += 1
if times > 3 {
// if max retry times reached then end
this.EndFlashRedirect()
goto end
}
// match uri or flash flag
if uri == value || flag == value {
match = true
} else {
// if no match then continue redirect
this.FlashRedirect(uri, code, flag, flagVal, times)
redirect = true
}
}
end:
return match, redirect
}
// set flash redirect
func (this *BaseRouter) FlashRedirect(uri string, code int, flag string, args ...interface{}) {
flagVal := "true"
times := 0
for _, arg := range args {
switch v := arg.(type) {
case string:
flagVal = v
case int:
times = v
}
}
if len(uri) == 0 || uri[0] != '/' {
panic("flash reirect only support same host redirect")
}
params := []interface{}{uri, code, flag, flagVal, times}
this.SetSession("on_redirect", params)
this.FlashWrite(flag, flagVal)
this.Redirect(uri, code)
}
// clear flash redirect
func (this *BaseRouter) EndFlashRedirect() {
this.DelSession("on_redirect")
}
// check form once, void re-submit
func (this *BaseRouter) FormOnceNotMatch() bool {
notMatch := false
recreat := false
// get token from request param / header
var value string
if vus, ok := this.Input()["_once"]; ok && len(vus) > 0 {
value = vus[0]
} else {
value = this.Ctx.Input.Header("X-Form-Once")
}
// exist in session
if v, ok := this.GetSession("form_once").(string); ok && v != "" {
// not match
if value != v {
notMatch = true
} else {
// if matched then re-creat once
recreat = true
}
}
this.FormOnceCreate(recreat)
return notMatch
}
// create form once html
func (this *BaseRouter) FormOnceCreate(args ...bool) {
var value string
var creat bool
creat = len(args) > 0 && args[0]
if !creat {
if v, ok := this.GetSession("form_once").(string); ok && v != "" {
value = v
} else {
creat = true
}
}
if creat {
value = utils.GetRandomString(10)
this.SetSession("form_once", value)
}
this.Data["once_token"] = value
this.Data["once_html"] = template.HTML(`<input type="hidden" name="_once" value="` + value + `">`)
}
func (this *BaseRouter) validForm(form interface{}, names ...string) (bool, map[string]*validation.Error) {
// parse request params to form ptr struct
utils.ParseForm(form, this.Input())
// Put data back in case users input invalid data for any section.
name := reflect.ValueOf(form).Elem().Type().Name()
if len(names) > 0 {
name = names[0]
}
this.Data[name] = form
errName := name + "Error"
// check form once
if this.FormOnceNotMatch() {
return false, nil
}
// Verify basic input.
valid := validation.Validation{}
if ok, _ := valid.Valid(form); !ok {
errs := valid.ErrorMap()
this.Data[errName] = &valid
return false, errs
}
return true, nil
}
// valid form and put errors to tempalte context
func (this *BaseRouter) ValidForm(form interface{}, names ...string) bool {
valid, _ := this.validForm(form, names...)
return valid
}
// valid form and put errors to tempalte context
func (this *BaseRouter) ValidFormSets(form interface{}, names ...string) bool {
valid, errs := this.validForm(form, names...)
this.setFormSets(form, errs, names...)
return valid
}
func (this *BaseRouter) SetFormSets(form interface{}, names ...string) *utils.FormSets {
return this.setFormSets(form, nil, names...)
}
func (this *BaseRouter) setFormSets(form interface{}, errs map[string]*validation.Error, names ...string) *utils.FormSets {
formSets := utils.NewFormSets(form, errs, this.Locale)
name := reflect.ValueOf(form).Elem().Type().Name()
if len(names) > 0 {
name = names[0]
}
name += "Sets"
this.Data[name] = formSets
return formSets
}
// add valid error to FormError
func (this *BaseRouter) SetFormError(form interface{}, fieldName, errMsg string, names ...string) {
name := reflect.ValueOf(form).Elem().Type().Name()
if len(names) > 0 {
name = names[0]
}
errName := name + "Error"
setsName := name + "Sets"
if valid, ok := this.Data[errName].(*validation.Validation); ok {
valid.SetError(fieldName, this.Tr(errMsg))
}
if fSets, ok := this.Data[setsName].(*utils.FormSets); ok {
fSets.SetError(fieldName, errMsg)
}
}
// check xsrf and show a friendly page
func (this *BaseRouter) CheckXsrfCookie() bool {
return this.Controller.CheckXSRFCookie()
}
func (this *BaseRouter) SystemException() {
}
func (this *BaseRouter) IsAjax() bool {
return this.Ctx.Input.Header("X-Requested-With") == "XMLHttpRequest"
}
func (this *BaseRouter) SetPaginator(per int, nums int64) *utils.Paginator {
p := utils.NewPaginator(this.Ctx.Request, per, nums)
this.Data["paginator"] = p
return p
}
func (this *BaseRouter) JsStorage(action, key string, values ...string) {
value := action + ":::" + key
if len(values) > 0 {
value += ":::" + values[0]
}
this.Ctx.SetCookie("JsStorage", value, 1<<31-1, "/", nil, nil, false)
}
func (this *BaseRouter) setLangCookie(lang string) {
this.Ctx.SetCookie("lang", lang, 60*60*24*365, "/", nil, nil, false)
}
// setLang sets site language version.
func (this *BaseRouter) setLang() bool {
isNeedRedir := false
hasCookie := false
// get all lang names from i18n
langs := setting.Langs
// 1. Check URL arguments.
lang := this.GetString("lang")
// 2. Get language information from cookies.
if len(lang) == 0 {
lang = this.Ctx.GetCookie("lang")
hasCookie = true
} else {
isNeedRedir = true
}
// Check again in case someone modify by purpose.
if !i18n.IsExist(lang) {
lang = ""
isNeedRedir = false
hasCookie = false
}
// 3. check if isLogin then use user setting
if len(lang) == 0 && this.IsLogin {
lang = i18n.GetLangByIndex(this.User.Lang)
}
// 4. Get language information from 'Accept-Language'.
if len(lang) == 0 {
al := this.Ctx.Input.Header("Accept-Language")
if len(al) > 4 {
al = al[:5] // Only compare first 5 letters.
if i18n.IsExist(al) {
lang = al
}
}
}
// 4. DefaucurLang language is English.
if len(lang) == 0 {
lang = "en-US"
isNeedRedir = false
}
// Save language information in cookies.
if !hasCookie {
this.setLangCookie(lang)
}
// Set language properties.
this.Data["Lang"] = lang
this.Data["Langs"] = langs
this.Lang = lang
return isNeedRedir
}
详情见:https://github.com/beego/wetalk