QOR: Golang开发的电商系统和CMS工具库--读官方文档
QOR: Golang开发的电商系统和CMS工具库--示例学习
基于Go语言开发的电商系统和CMS的SDK,根据网友的说法:QOR可以看作是PHP中的ThinkPHP,Python中的Django。
网友专栏介绍学习,不过看文章是2018年的了。相关的文章有限,估计又得啃外文学习。
https://github.com/golangpkg/qor-cms-demos 各章示例代码,虽然也是3年前的。
首先,QOR还不算是CMS,它比起Web框架来说进行了更进一步的封装,将Web开发中常用的部分封装成用起来更简单的库。用它来创建一个基于内容管理的Web应用更加快速简单。
开始
package main
import (
"fmt"
"net/http"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/qor/admin"
)
// 用户
type User struct {
gorm.Model
Name string
}
// 产品
type Product struct {
gorm.Model
Name string
Description string
}
func main() {
// 注册数据库,可以是sqlite3 也可以是 mysql 换下驱动就可以了。
DB, _ := gorm.Open("sqlite3", "demo.db")
DB.AutoMigrate(&User{}, &Product{}) //自动创建表。
// 初始化admin 还有其他的,比如API
Admin := admin.New(&admin.AdminConfig{DB: DB})
// 创建admin后台对象资源。
Admin.AddResource(&User{})
Admin.AddResource(&Product{})
// 启动服务
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
fmt.Println("Listening on: 9000")
http.ListenAndServe(":9000", mux)
}
打开浏览器 http://127.0.0.1:9000/admin,一个简单的后台就建立起来了。
在后端将显示一个Users菜单,可以添加User。只有一个项目为Name。产品项也一样。就这么几句,就实现了一个简单的后台。
认证
QOR提供了一个认证系统Auth,它是一个模块化的认证系统,用于Golang的web开发,它提供了不同的认证后端来加速你的开发。
目前认证有数据库密码,github,谷歌,facebook, twitter认证支持,并且很容易添加其他支持基于Auth的提供者接口。国内还应该扩展qq认证,微信认证,支付宝认证。
要使用它,基本流程是:
使用配置初始化身份验证
注册一些提供商
将其注册到路由器
package main
import (
"net/http"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/qor/admin"
"github.com/qor/auth"
"github.com/qor/auth/auth_identity"
"github.com/qor/auth/providers/facebook"
"github.com/qor/auth/providers/github"
"github.com/qor/auth/providers/google"
"github.com/qor/auth/providers/password"
"github.com/qor/auth/providers/twitter"
"github.com/qor/session/manager"
)
var (
gormDB, _ = gorm.Open("sqlite3", "sample.db") //初始化数据库
Auth = auth.New(&auth.Config{DB: gormDB}) //认证初始化
Admin = admin.New(&admin.AdminConfig{DB: gormDB})
)
func init() {
gormDB.AutoMigrate(&auth_identity.AuthIdentity{}, &auth_identity.AuthIdentity{}) //设置认证模块,自动创建表。
// 注册身份验证提供程序
// 允许使用用户名/密码
Auth.RegisterProvider(password.New(&password.Config{}))
// 允许使用Github
Auth.RegisterProvider(github.New(&github.Config{
ClientID: "github client id",
ClientSecret: "github client secret",
}))
// 允许使用Google
Auth.RegisterProvider(google.New(&google.Config{
ClientID: "google client id",
ClientSecret: "google client secret",
}))
// 允许使用Facebook
Auth.RegisterProvider(facebook.New(&facebook.Config{
ClientID: "facebook client id",
ClientSecret: "facebook client secret",
}))
// 允许使用Twitter
Auth.RegisterProvider(twitter.New(&twitter.Config{
ClientID: "twitter client id",
ClientSecret: "twitter client secret",
}))
// 创建admin后台对象资源。
Admin.AddResource(&auth_identity.AuthIdentity{})
}
func main() {
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
mux.Handle("/auth/", Auth.NewServeMux()) //路由
http.ListenAndServe(":9000", manager.SessionManager.Middleware(mux))
}
admin http://localhost:9000/admin/auth_identities
注册 http://localhost:9000/auth/register
登陆 http://localhost:9000/auth/login
以上示例在官方文档基础上作了修改才通过运行,郁闷。注册和登陆界面也没有显示表单等,倒是后台能添加用户。
Auth有两个模型,模型AuthIdentityModel用于保存登录信息,模型UserModel用于保存用户信息。之所以将身份验证和用户信息保存为两种不同的模型,是因为希望能够将用户链接到多个身份验证信息记录,这样用户就可以有多种登录方式。
如果您不需要这样做,那么您可以将这两个模型设置为相同的一个,或者跳过设置UserModel。就看示例中gormDB.AutoMigrate(&auth_identity.AuthIdentity{}, &auth_identity.AuthIdentity{})
不同的提供者(你可以在后台看到provider)通常使用不同的信息登录,例如提供者password使用用户名/密码,github使用github用户ID,因此对于每个提供者,它将这些信息保存到自己的记录中。
不需要设置AuthIdentityModel,Auth具有默认的AuthIdentityModel定义。如果需要修改,则在库中的auth_identity表修改。
默认情况下,没有定义UserModel,即使您仍然可以使用身份验证功能,身份验证将返回使用身份验证信息记录作为登录的用户。
但是通常情况下,您的应用程序将具有User模型,在设置其值之后,当您从任何提供商注册新帐户时,Auth将使用UserStorer创建/获取用户,并将其ID链接到auth身份记录。
自定义视图
认证使用Render来渲染页面,可以引用它来了解如何注册func映射,扩展视图路径。如果想把它编译到包中一起,需要有BinddataFS。
如果要优先使用视图路径,则可以将其添加到ViewPaths中,如果您想覆盖默认的(丑陋的)登录/注册页面,可以看诸如https://github.com/qor/auth_themes之类的身份验证主题。
发送邮件
使用Mailer进行验证以发送电子邮件,默认情况下,Auth会将打印的电子邮件打印到控制台,请配置它发送真实的电子邮件。
用户存储
Auth根据您的AuthIdentityModel,创建一个默认的UserStorer来获取/保存用户。更改它可以实现自己的用户存储。
会话存储
Auth还有一个处理会话、flash消息的默认方法,可以通过实现会话存储接口来覆盖。
默认情况下,Auth使用会话的默认管理器保存数据到cookie,但为了正确保存cookie,你必须注册会话的中间件到你的路由器,例如:
func main() {
mux := http.NewServeMux()
// Register Router
mux.Handle("/auth/", Auth.NewServeMux())
http.ListenAndServe(":9000", manager.SessionManager.Middleware(mux))
}
重定向器
在一些身份验证操作之后,如登录、注册或确认,身份验证将用户重定向到某个URL,您可以配置用重定向器重定向哪个页面,默认情况下,将重定向到主页。
如果您想重定向到上次访问的页面,可以使用redirect_back。
var RedirectBack = redirect_back.New(&redirect_back.Config{
SessionManager: manager.SessionManager,
IgnoredPrefixes: []string{"/auth"},
}
var Auth = auth.New(&auth.Config{
...
Redirector: auth.Redirector{RedirectBack},
})
为了使其正常工作,redirect_back需要将每个访问的最后访问的URL保存到与会话管理器的会话中,这意味着您需要将redirect_back和SessionManager的中间件安装到路由器中。
http.ListenAndServe(":9000", manager.SessionManager.Middleware(RedirectBack.Middleware(mux)))
主题
为了节省更多开发人员的努力,已经创建了一些认证主题。它通常具有设计良好的页面,如果您不需要太多自定义要求,则可以只用几行就可以使Auth系统准备好用于您的应用程序,例如:
import "github.com/qor/auth_themes/clean"
var Auth = clean.New(&auth.Config{
DB: db.DB,
Render: config.View,
Mailer: config.Mailer,
UserModel: models.User{},
})
授权
身份验证是验证你是谁的过程,授权是验证你能访问某个东西的过程。认证包不仅提供认证
Admin
QOR Admin是一个Golang框架,允许您在几分钟内创建一个美丽的,跨平台的,可配置的管理界面,管理您的数据。
常规配置
您可以在初始化Admin时使用AdminConfig struct自定义Admin,以下是一些通用配置
type AdminConfig struct {
SiteName string
DB *gorm.DB
Auth Auth
SessionManager session.ManagerInterface
I18n I18n
AssetFS assetfs.Interface
*Transformer
}
Admin := admin.New(&admin.AdminConfig{SiteName: “Qor Example”})
AssetFS定义了呈现页面时如何查找模板
仪表盘
QOR Admin提供了一个默认的仪表板页面,其中包含一些虚拟文本。 如果要自定义仪表板,则可以在QOR视图路径中创建一个文件dashboard.tmpl,QOR Admin在呈现仪表板页面时会将其作为Golang模板加载。
如果要禁用仪表板,可以将其重定向到其他页面
Admin.GetRouter().Get("/", func(c *admin.Context) {
http.Redirect(c.Writer, c.Request, "/admin/clients", http.StatusSeeOther)
})
资源
资源可以通过QOR管理员的用户界面(通常是GORM后端模型)进行管理。
将资源添加到QOR Admin
// GORM-backend model
// 这里的顺序也将影响后台的显示顺序
type User struct {
gorm.Model
Email string
Password string
Name sql.NullString
Gender string
Role string
Addresses []Address //在实际运行中,提示没有定义Address
}
// Add it to Admin
user := Admin.AddResource(&User{}, &admin.Config{Menu: []string{"User Management"}}) //实际运行中,将此改为中文将失去前导用户图标
资源配置
在admin.Config中自定义资源时可用的选项是:
名称 | 类型 | 缺省 | 说明 |
---|---|---|---|
Name | string | 显示资源的名称 | |
Menu | []string | 资源的菜单设置 | |
Permission | *roles.Permission | 控制资源的权限 | |
Themes | []ThemeInterface | 设置资源的自定义主题 | |
Priority | int | 控制菜单中的显示顺序,按ASC顺序排列 | |
Singleton | bool | false | 设置资源是单个对象还是多个对象。 |
Invisible | bool | false | 设置资源在菜单中是否可见 |
PageCount | int | 20 | 分页设置,设置每页显示多少条记录 |
字段
自定义可见字段
默认情况下,所有字段都可见。如果您明确声明字段,则只有定义的字段可见,并且它们将按定义的顺序显示:
// 将资源“Order”,“Product”添加到管理
order := Admin.AddResource(&models.Order{})
product := Admin.AddResource(&models.Product{})
// 显示给定的属性
order.IndexAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
// 除 `State` 外显示所有
order.IndexAttrs("-State")
// 设置属性将显示在新页面中
order.NewAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
// 除 `State` 外显示所有
order.NewAttrs("-State")
// 使用“Section”构造新表单,使其整洁干净
product.NewAttrs(
&admin.Section{
Title: "Basic Information",
Rows: [][]string{
{"Name"},
{"Code", "Price"},
}
},
&admin.Section{
Title: "Organization",
Rows: [][]string{
{"Category", "Collections", "MadeCountry"},
}
},
"Description",
"ColorVariations",
}
// 设置属性将显示在编辑页面上,类似于新页面
order.EditAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
// 设置属性将显示在显示页面上,类似于新页面
// 如果尚未配置ShowAttrs,则不会生成任何显示页面,而是显示编辑表单
order.ShowAttrs("User", "PaymentAmount", "ShippedAt", "CancelledAt", "State", "ShippingAddress")
我修改了以上的示例代码,但依然提示“product.NewAttrs is not a type”。
自定义嵌套资源的字段
order := Admin.AddResource(&models.Order{})
orderItemMeta := order.Meta(&admin.Meta{Name: "OrderItems"})
orderItemResource := orderItemMeta.Resource
orderItemResource.EditAttrs("ProductCode", "Price", "Quantity")
元Meta
默认情况下,资源的字段是根据其类型和关系呈现的。 默认值应满足通常情况,您可以通过覆盖Meta定义来自定义呈现。
这是一个有关使用户的“性别”作为用户表单中的选择元素的示例,该元素包含三个选项“男性”,“女性”和“未知”。
user.Meta(&admin.Meta{Name: “Gender”, Config: &admin.SelectOneConfig{Collection: []string{“Male”, “Female”, “Unknown”}}})
自定义元
如果字段的默认配置不符合您的需求,则您想自定义字段:
type Meta struct {
Name string
FieldName string
Label string
Type string
Setter func(object interface{}, metaValue *resource.MetaValue, context *qor.Context)
Valuer func(object interface{}, context *qor.Context) (value interface{})
FormattedValuer func(object interface{}, context *qor.Context) (formattedValue interface{})
Permission *roles.Permission
Config MetaConfigInterface
Collection interface{}
Resource *Resource
}
. Name 要覆盖的字段的名称
user.Meta(&admin.Meta{Name: “Gender”, Config: &admin.SelectOneConfig{Collection: []string{“Male”, “Female”, “Unknown”}}})
. FieldName 映射到资源中的属性名称,通常不需要设置,并且默认情况下与Name相同。
当您要将QOR Admin用作RESTFul API服务并公开具有不同名称的字段时,通常需要这样做,例如:
order.Meta(&admin.Meta{Name: “Code”, FieldName: “ExternalCode”})
. Type 属性的显示类型
. Label 表单中属性的标签和索引页的表标题。
默认情况下 “address” 标签是 “Address”.
. Setter Setter定义了如何将表单值解码为字段,这是QOR Admin生成的默认Setter,它从metaValue获取表单值,并根据字段的类型对该值进行解码以进行记录。
. Valuer Valuer定义了如何从对象中获取字段的值,它返回一个golang对象作为结果,QOR通常会根据字段的值和状态以不同的方式呈现字段模板。
. FormattedValuer FormattedValuer与Valuer类似,但它通常返回格式化的字符串作为结果,它将在索引页和API中显示给最终用户。
. Resource 这可用于自定义嵌套形式的属性,通常无需设置,请查看如何自定义嵌套形式的属性以获取详细信息。
. Permission 定义此属性的用户权限
. Config 当前属性类型的配置
&admin.SelectOneConfig{Collection: []string{“Male”, “Female”, “Unknown”}}
虚拟字段
您可以配置QOR Admin以显示“虚拟”字段—这些字段不是数据库属性。
如果你想在NewAttrs, EditAttrs, ShowAttrs中使用虚拟字段,你必须这样做:
定义元数据的Type
定义元的Setter
常见的元类型
String/Text
Checkbox
Number/Float
Date/Datetime
Hidden/Readonly
Password
Rich editor
Single edit
Collection edit (集合编辑)
Select one
Select many
user.Meta(&admin.Meta{Name: "Password",
Type: "password",
Valuer: func(interface{}, *qor.Context) interface{} { return "" },
Setter: func(record interface{}, metaValue *resource.MetaValue, context *qor.Context) {
if newPassword := utils.ToString(metaValue.Value); newPassword != "" {
bcryptPassword, _ := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
record.(*models.User).EncryptedPassword = string(bcryptPassword)
}
},
})
搜索&范围&过滤器
搜索
您可以使用SearchAttrs配置资源的可搜索属性,如果未设置SearchAttrs,则将使用资源的IndexAttrs进行搜索
product.SearchAttrs("Name", "Code", "Category.Name", "Brand.Name")
将资源添加到管理员搜索中心
QOR管理员提供了一个搜索中心,你可以用AddSearchResource注册可搜索资源,有了搜索中心,你可以在一个请求中搜索多个资源
Admin.AddSearchResource(product, user, order)
您可以覆盖它,用自定义资源的搜索
oldSearchHandler := product.SearchHandler
product.SearchHandler = func(keyword string, context *qor.Context) *gorm.DB {
context.SetDB(context.GetDB().Preload("Variations.Color").Preload("Variations.Size").Preload("Variations.Material"))
return oldSearchHandler(keyword, context)
}
范围
user.Scope(&admin.Scope{Name: "Active", Handler: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("active = ?", true)
}})
在列表左上角显示按钮,进行部份属性过滤显示。
组范围
将相似的作用域放入一个组,请为其设置组名称
order.Scope(&admin.Scope{Name: "Paid", Group: "State", Handler: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("state = ?", "paid")
}})
order.Scope(&admin.Scope{Name: "Shipped", Group: "State", Handler: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("state = ?", "shipped")
}})
默认范围
默认范围将应用于所有请求
order.Scope(&admin.Scope{
Name: "Default Scope",
Default: true,
Handler: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("state = ?", "paid")
},
})
根据条件的可见范围
基于可见的返回true使范围可见
order.Scope(&admin.Scope{Name: "Paid", Group: "State",
Visible: func(context *admin.Context) bool {
return context.CurrentUser.IsAdmin
},
Handler: func(db *gorm.DB, context *qor.Context) *gorm.DB {
return db.Where("state = ?", "paid")
},
})
基于条件的可见过滤器
user.Filter(&admin.Filter{
Name: "Gender",
Visible: func(context *admin.Context) bool {
return context.CurrentUser.IsAdmin
},
Config: &admin.SelectOneConfig{
Collection: []string{"Male", "Female", "Unknown"},
},
})
过滤
在给定的设置下,在QOR Admin中使任何资源可过滤。
以下示例显示了如何在假设的项目中按性别(“男”,“女”或“未知”)过滤用户。
// Filter users by gender
user.Filter(&admin.Filter{
Name: "Gender",
Config: &admin.SelectOneConfig{
Collection: []string{"Male", "Female", "Unknown"},
},
})
// Filter products by collection
product.Filter(&admin.Filter{
Name: "Collections",
Config: &admin.SelectOneConfig{RemoteDataResource: collection},
})
操作
使用
让我们为用户定义一个Enable操作,查看Action的工作方式。
type User struct {
gorm.Model
Name string
Active bool
}
user := Admin.AddResource(&models.User{})
user.Action(&admin.Action{
Name: "enable",
Handle: func(actionArgument *admin.ActionArgument) error {
// `FindSelectedRecords` => 在批量操作模式下,将返回所有已检查的记录,在其他模式下,将返回当前记录
for _, record := range actionArgument.FindSelectedRecords() {
actionArgument.Context.DB.Model(record.(*models.User)).Update("Active", true)
}
return nil
},
})
用户的索引和编辑页面将显示一个按钮“ENABLE”,就像这样
配置
type Action struct {
Name string
Label string
Method string
URL func(record interface{}, context *Context) string
URLOpenType string
Visible func(record interface{}, context *Context) bool
Handler func(argument *ActionArgument) error
Modes []string
Resource *Resource
Permission *roles.Permission
}
Method 方法,HTTP方法,默认设置为PUT。当有URL选项时,默认为GET
URL 设置URL,动作按钮将触发请求。此选项将覆盖处理程序选项。例如,通过URL检查动作。
URLOpenType 设置打开URL的方式。具有Resource的操作的默认值为bottomsheet(链接将在弹出窗口中打开)。 对于没有资源的动作是_blank。
Visible 设置此操作何时可见的条件,例如,根据条件检查“可见操作”。
Handler 处理动作请求的函数
Permission 权限控制
Resource 设置资源以存储用户输入。
Modes 支持5个选项:“batch”批处理、“edit”编辑、“show”显示、“menu_item”菜单项、collection集合,5种模式映射到这些页面:
batch,当启用大容量编辑模式时,大容量操作将显示在索引列表页中
collection,将显示在索引列表页面中
show,显示页面动作,将显示在显示页面中。
edit,编辑表单操作,将显示在编辑页面上。
menu_item,菜单项操作,将显示在表的菜单中。
基于条件的可见动作
仅当订单处于“draft”和“processing”状态时,使用“Visible”控制显示“Cancel”功能,可以用来取消订单。“Visible”选项的记录参数是当前订单, 当返回值为false时,该操作(Cancel)对用户不可见。
order.Action(&admin.Action{
Name: "Cancel",
Handler: func(argument *admin.ActionArgument) error {
// cancel the order
},
Visible: func(record interface{}, context *Context) bool {
if order, ok := record.(*models.Order); ok {
for _, state := range []string{"draft", "processing"} {
if order.State == state {
return true
}
}
}
return false
},
Modes: []string{"show", "menu_item"},
})
URL动作
此示例显示了如何执行一项操作,使用户可以单击该操作以查看前端的产品详细信息页面。 URL函数的record参数是当前产品,我们通过产品代码将URL设置为前端。
product.Action(&admin.Action{
Name: "View On Site",
URL: func(record interface{}, context *admin.Context) string {
if product, ok := record.(*models.Product); ok {
return fmt.Sprintf("/products/%v", product.Code)
}
return "#"
},
Modes: []string{"menu_item", "edit", "show"},
})
批处理
使用QOR Admin创建批处理操作相当容易,只需使用“操作模式”在索引页面上启用某个操作,该操作便成为批处理操作,例如:
order.Action(&admin.Action{
Name: "Cancel",
Handler: func(argument *admin.ActionArgument) error {
// cancel the order
},
Visible: func(record interface{}, context *Context) bool {
if order, ok := record.(*models.Order); ok {
for _, state := range []string{"draft", "processing"} {
if order.State == state {
return true
}
}
}
return false
},
Modes: []string{"batch"},
})
用户输入的动作
您需要定义一个资源来接受用户的输入,可以在Handler函数中使用它。
// ship 行为参数
type trackingNumberArgument struct {
TrackingNumber string
}
trackingNumberRes := Admin.NewResource(&trackingNumberArgument{})
order.Action(&admin.Action{
Name: "Ship",
Handler: func(argument *admin.ActionArgument) error {
// 从参数获取用户输入。
trackingNumberArgument := argument.Argument.(*trackingNumberArgument)
for _, record := range argument.FindSelectedRecords() {
argument.Context.GetDB().Model(record).UpdateColumn("tracking_number", trackingNumberArgument.TrackingNumber)
}
return nil
},
Resource: trackingNumberRes,
Modes: []string{"show", "menu_item"},
})
数据处理与验证
验证方式
应用程序级别验证
如果要对应用程序进行一些全局验证,则可以使用QOR验证,它是GORM扩展,可以在创建,更新时用于验证模型。
管理员级别验证
如果您只想验证来自QOR Admin的数据,那么QOR Admin Validator适合您,它将在将表单/ JSON数据解码到结构之前检查数据。
store := Admin.AddResource(&Store{})
store.AddValidator(&resource.Validator{
Name: "check_has_name" // 注册另一个具有相同名称的验证器将覆盖先前的验证器
Handler: func(record interface{}, metaValues *resource.MetaValues, context *qor.Context) error {
// 从metaValues获取meta的值,metaValues是保存所有发布数据的结构
if meta := metaValues.Get("Name"); meta != nil {
if name := utils.ToString(meta.Value); strings.TrimSpace(name) == "" {
return validations.NewError(record, "Name", "Name can't be blank")
}
}
return nil
},
})
在保存到数据库之前处理数据
应用程序级处理器
如果您想在将数据保存到数据库之前处理一些数据,并将其全局保存,那么GORM回调非常适合您的情况。
管理员级别处理器
但是,当您只想处理来自QOR Admin的数据时,可以使用QOR Admin Processor,它可以在将数据解码到结构中之后处理数据,但是在将它们保存到数据库中之前,可以像这样使用它:
store.AddProcessor(&resource.Processor{
Name: "process_store_data", // 注册另一个具有相同名称的处理器将覆盖先前的处理器
Handler: func(value interface{}, metaValues *resource.MetaValues, context *qor.Context) error {
if store, ok := value.(*Store); ok {
// do something...
}
return nil
},
})
RESTFul API
QOR管理员为注册资源生成RESTFul API
基本用法
func main() {
API := admin.New(&qor.Config{DB: db.DB})
user := API.AddResource(&User{})
mux := http.NewServeMux()
API.MountTo("/api", mux)
http.ListenAndServe(":3000", mux); err != nil {
}
一旦你定义了你的用户资源,QOR管理员将为它生成RESTFul API
GET /api/users - 检索用户列表
GET /api/users/12 - 检索特定用户
POST /api/users - 创建一个新用户
PUT /api/users/12 - 更新用户 #12
DELETE /api/users/12 - 删除用户 #12
请求扩展名为.json的API,例如/api/users.json,QOR Admin将返回JSON格式的数据,扩展名为.xml(/api/users.xml)的请求将以XML strucutre返回数据。
(两种格式都有了,啥操作也不用管,简单实用。看看该如何控制权限。)
自定义字段
API中的自定义字段与普通资源相同,你可以使用IndexAttrs, ShowAttrs来为API用户配置可见字段。
动作
有时,需要公开API中固有的非RESTful操作。
此类操作的一个示例是您要为资源引入状态更改,可以使用QOR Admin进行设计,例如:
user.Action(&admin.Action{
Name: "enable",
Handle: func(actionArgument *admin.ActionArgument) error {
// `FindSelectedRecords` => in bulk action mode, will return all checked records, in other mode, will return current record
for _, record := range actionArgument.FindSelectedRecords() {
actionArgument.Context.DB.Model(record.(*models.User)).Update("Active", true)
}
return nil
},
})
user.Action(&admin.Action{
Name: "disable",
Handle: func(actionArgument *admin.ActionArgument) error {
// `FindSelectedRecords` => in bulk action mode, will return all checked records, in other mode, will return current record
for _, record := range actionArgument.FindSelectedRecords() {
actionArgument.Context.DB.Model(record.(*models.User)).Update("Active", false)
}
return nil
},
})
它将生成如下的API:
PUT /api/users/12/enable - enable user #12
PUT /api/users/12/disable - disable user #12
嵌套的API
type User struct {
gorm.Model
Name string `form:"name"`
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
User User
Amount float32
OrderItems []OrderItem
}
user := API.AddResource(&User{})
// 使用用户的字段名注册嵌套的API
userOrders, _ := user.AddSubResource("Orders")
这将生成API:
GET /api/users/12/orders - Retrieves a list of orders from user #12
GET /api/users/12/orders/22 - Retrieves order #22 from user #12
POST /api/users/12/orders - Creates a new order for user #12
PUT /api/users/12/orders/22 - Updates user #12's orders #22
DELETE /api/users/12/orders/22 - Deletes users #12's orders #22
认证与授权
与普通管理网站相同,您可以使用身份验证和授权来保护您的API。
并使用Permission进行资源级别,字段级别权限控制
认证与授权
认证方式
QOR Admin通过为常见的Authentication相关任务提供界面,允许您集成当前的身份验证方法。
您需要做的是实现如下所示的认证接口,并在QOR Admin值中设置它。
type Auth interface {
GetCurrentUser(*Context) qor.CurrentUser // 获取当前用户,如果没有权限,返回nil
LoginURL(*Context) string // 获取登录URL,如果没有权限,将重定向到该URL
LogoutURL(*Context) string // 获取注销URL,如果单击管理界面中的注销链接,将访问此页面
}
在初始化QOR Admin时进行设置时,例如:
func main() {
// 初始化QOR Admin时设置身份验证界面
Admin := admin.New(&admin.AdminConfig{
Auth: yourAuthInterface,
})
}
下面是一个整合了QOR Auth和QOR Auth主题的例子:
import "github.com/qor/auth_themes/clean"
var Auth = clean.New(&auth.Config{
DB: DB,
// User model needs to implement qor.CurrentUser interface (https://godoc.org/github.com/qor/qor#CurrentUser) to use it in QOR Admin
UserModel: models.User{},
})
type AdminAuth struct {}
func (AdminAuth) LoginURL(c *admin.Context) string {
return "/auth/login"
}
func (AdminAuth) LogoutURL(c *admin.Context) string {
return "/auth/logout"
}
func (AdminAuth) GetCurrentUser(c *admin.Context) qor.CurrentUser {
currentUser := Auth.GetCurrentUser(c.Request)
if currentUser != nil {
qorCurrentUser, ok := currentUser.(qor.CurrentUser)
if !ok {
fmt.Printf("User %#v haven't implement qor.CurrentUser interface\n", currentUser)
}
return qorCurrentUser
}
return nil
}
func main() {
// Set Auth interface when initialize QOR Admin
Admin := admin.New(&admin.AdminConfig{
Auth: &AdminAuth{},
})
}
授权
资源授权
Admin.AddResource(&Product{}, &admin.Config{
Permission: roles.Deny(roles.Delete, roles.Anyone).Allow(roles.Delete, "admin")
})
字段授权
product := Admin.AddResource(&Product{})
product.Meta(&admin.Meta{Name: "Price", Permission: roles.Allow(roles.Update, "admin")})
行为授权
QOR Admin将检查权限模式角色。在检查当前用户是否具有调用操作的能力时更新,其他模式将被忽略。
user.Action(&admin.Action{
Name: "enable",
Permission: roles.Allow(roles.Update, "admin"),
Handle: func(actionArgument *admin.ActionArgument) error {
// `FindSelectedRecords` => in bulk action mode, will return all checked records, in other mode, will return current record
for _, record := range actionArgument.FindSelectedRecords() {
actionArgument.Context.DB.Model(record.(*models.User)).Update("Active", true)
}
return nil
},
})
菜单授权
QOR Admin将检查权限模式角色。在检查当前用户是否具有查看菜单的能力时读取,其他模式将被忽略。
Admin.AddMenu(&admin.Menu{Name: “Report”, Link: “/admin”, Permission: roles.Allow(roles.Read, “admin”)})
国际化
QOR Admin提供了一个支持国际化的接口
type I18n interface {
Scope(scope string) I18n
Default(value string) I18n
T(locale string, key string, args ...interface{}) template.HTML
}
如果您在初始化QOR Admin时指定了实现此接口的i18n后端,它将立即获得国际化支持。
Admin := admin.New(&admin.AdminConfig{
I18n: yourI18nBackend,
})
QOR I18n
QOR I18n实现了I18n界面,下面让我向您展示如何在Qor Admin中使用它。
首先,使用存储初始化i18n。 您可以一起使用多个存储,较早的存储具有更高的优先级。 因此,在此示例中,I18n将首先在数据库中查找翻译,然后如果未找到,则继续在YAML文件中找到它。
import (
"github.com/jinzhu/gorm"
"github.com/qor/i18n"
"github.com/qor/i18n/backends/database"
"github.com/qor/i18n/backends/yaml"
)
func main() {
db, _ := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
I18n := i18n.New(
database.New(&db), // load translations from the database
yaml.New(filepath.Join(config.Root, "config/locales")), // 从config/locales目录中加载YAML文件翻译
)
}
然后使用I18n初始化QOR Admin
Admin := admin.New(&admin.AdminConfig{
I18n: I18n,
})
完成了! 所有翻译将由I18n后端提供支持。
通过QOR管理界面管理I18n翻译
没有人愿意通过更改数据库来更新翻译或直接更新YAML,因此i18n提供了一个友好的Web界面,用于通过QOR Admin管理翻译,使其生效,只需将其作为资源添加到QOR Admin中即可,例如:
Admin.AddResource(I18n)
如果您将I18n作为资源注册到QOR Admin,则可以在初始化时跳过配置QOR Admin,i18n会自动执行
主题与定制
全局样式表/脚本代码
QOR管理员将根据您管理员的SiteName自动加载javascript和样式表文件
例如,假设您将站点名称设置为Qor Demo,则QOR管理员将查找 {qor view paths}/assets/javascripts/qor_demo.js 和{qor view paths}/assets/stylesheets/qor_demo.css,如果存在则加载它们。
视图路径
当QOR管理呈现页面时,它用AssetFS查找模板。
AssetFS的默认实现是使用预先注册的视图路径从文件系统查找模板。
它包括:
{current_path}/app/views/qor
{current_path}/vendor/github.com/qor/admin/views
$GOPATH/src/github.com/qor/admin/views
主题
QOR管理提供灵活的模板定制。您可以为资源定义自己的主题。
QOR Admin中资源的自定义主题可以使用自定义javascript和CSS文件。
要应用自定义主题,请使用UseTheme方法设置主题名称,将从主题路径载入 assets/javascripts/fancy.js 和 assets/stylesheets/fancy.css
例如:
product := Admin.AddResource(&Product{})
product.UseTheme("fancy")
这意味着当请求product页面时,将加载 {qor view paths}/themes/fancy/assets/stylesheets/fancy.css 和 {qor view paths}/themes/fancy/assets/javascripts/fancy.js
自定义模板
QOR管理员正在使用go模板来渲染管理界面,默认模板可以从这里找到 https://github.com/qor/admin/tree/master/views
您可能想要根据您的需求定制其中的一些。你可以把一个同名的新模板放到QOR视图路径中,QOR管理员会根据优先级加载模板。
QOR管理员将从这些路径查找模板,顶级路径将有更高的优先级
{qor view paths}/themes/{theme name}/{resource params}/{template}
{qor view paths}/themes/{theme name}/{template}
{qor view paths}/{resource params}/{template}
{qor view paths}/{template}
覆写layout.tmpl将影响整个网站
你可以创建文件名layout.tmpl放到{current_path}/app/views/qor
覆写layout.tmpl只影响一个资源
如果要覆盖指定资源的布局,你可以把文件放到 {qor view paths}/{resource's params}。例如:覆写Product的布局, 创建 layout.tmpl 文件,把它放在 {current_path}/app/views/qor/products 目录
覆写layout.tmpl影响集合资源
使用UseTheme为这些资源设置相同的主题名称
res.UseTheme('fancy') 将文件layout.tmpl放到 {current_path}/app/views/qor/themes/fancy/layout.tmpl
菜单
QOR管理提供了一个灵活的方式来管理菜单。默认情况下,资源将列在菜单的顶层。您可以手动设置位置。
Admin.AddResource(&User{})
Admin.AddResource(&Product{}, &admin.Config{Menu: []string{"Product Management"}})
Admin.AddResource(&Color{}, &admin.Config{Menu: []string{"Product Management"}})
Admin.AddResource(&Size{}, &admin.Config{Menu: []string{"Product Management"}})
Admin.AddResource(&Order{}, &admin.Config{Menu: []string{"Order Management"}})
如果不希望在菜单中显示资源,请使用Invisible选项
Admin.AddResource(&User{}, &admin.Config{Invisible: true})
用自定义URL注册菜单
您可以添加自定义URL菜单,像这样:
// 注册菜单 `Sales Report`, 在 "Reports" 下
Admin.AddMenu(&admin.Menu{Name: "Sales Report", Link: "/admin/sales_report", Ancestors: []string{"Reports"}})
// 注册菜单 `Sales Report` 并带相对路径, 最后的URL将是admin路径+RelativePath, 本例中为 `/admin/sales_report`
Admin.AddMenu(&admin.Menu{Name: "Sales Report", RelativePath: "/sales_report", Ancestors: []string{"Reports"}})
菜单权限
// 注册菜单有权限,用户有“admin”权限可以看到“Report”菜单。
Admin.AddMenu(&admin.Menu{Name: "Report", Link: "/admin/reports", Permission: roles.Allow(roles.Read, "admin")})
优先菜单
用优先级设置菜单的优先级,小的数字优先级高,负数优先级低
Admin.AddMenu(&admin.Menu{Name: "First Menu", Priority: 1})
Admin.AddMenu(&admin.Menu{Name: "Second Menu", Priority: 2})
Admin.AddMenu(&admin.Menu{Name: "Third Menu", Priority: 5})
Admin.AddMenu(&admin.Menu{Name: "Forth Menu", Priority: -2})
Admin.AddMenu(&admin.Menu{Name: "Last Menu", Priority: -1})
配置自己的菜单图标
QOR管理员使用来自 material icons 的图标,并在每个菜单上预先生成属性名称。所以很容易定制自己的菜单图标。
假设您正在向产品添加图标。首先,去材料图标页面,选择你想要的图标,然后复制内容在截图
然后把它放在CSS中,关于如何为QOR管理自定义CSS去查看QOR管理主题。
[qor-icon-name*="Products"] > a::before {
content: "\E2BF";
}
如果你不想使用资源名作为图标名。你可以像这样使用IconName
Admin.AddResource(&User{}, &admin.Config{IconName: "YouOwnIconName"})
菜单图标对应的css将是
[qor-icon-name*="YouOwnIconName"] > a::before {
content: "\E2BF";
}
扩展QOR管理
扩展QOR资源
将结构添加到QOR Admin后,QOR Admin将检查此结构及其嵌入式结构是否实现了接口ConfigureResourceBeforeInitializeInterface或ConfigureResourceInterface
ConfigureResourceBeforeInitializeInterface接口将在初始化资源之前被调用。
ConfigureResourceInterface接口将在初始化资源之后被调用。
因此,当AddResource时,工作流如下所示:
type User struct {
}
func (User) ConfigureQorResourceBeforeInitialize(resource.Resourcer) {
// 初始化前做一些事
}
func (User) ConfigureQorResource(resource.Resourcer) {
// 初始化后做一些事
}
user := Admin.AddResource(&User{})
// 1, 执行 User.ConfigureQorResourceBeforeInitialize(user)
// 2, 对资源应用默认设置
// 3, 执行 User.ConfigureQorResource(user)
当编写QOR插件时,这是很有帮助的,大多数插件都是基于此编写的,例如:QOR L10n, QOR Publish2
覆盖CURD处理程序
QOR Admin基于GORM的API生成默认的CURD处理程序,如果您的资源不是GORM后端模型,则可以考虑编写自己的CRUD处理程序,例如将其保存到Redis或缓存服务器中,例如:
res.FindOneHandler = func(result interface{}, metaValues *resource.MetaValues, context *qor.Context) error {
// 查找记录并将其解码以得到结果
}
res.FindManyHandler = func(results interface{}, context *qor.Context) error {
// 查找记录并将其解码为结果(找到多个记录)
}
res.SaveHandler = func(result interface{}, context *qor.Context) error {
// 保存结果
}
res.DeleteHandler = func(result interface{}, context *qor.Context) error {
// 删除结果
}
查看https://github.com/qor/qor/blob/master/resource/crud.go文件,可以从默认实现中获取一些提示。
属性
如您所知,您可以使用IndexAttrs,NewAttrs,EditAttrs,ShowAttrs来设置索引/显示/编辑/新页面的属性。
在编写插件时,您可能会要求始终显示或隐藏一些属性,OverrideIndexAttrs,OverrideNewAttrs,OverrideEditAttrs,OverrideShowAttrs可以发挥作用,您可以这样写:
// 每次您为资源配置EditAttrs时,我们都会附加字段`PublisReady`并从编辑属性中删除`State`。
res.OverrideEditAttrs(func() {
res.EditAttrs(res.EditAttrs(), "PublishReady", "-State")
})
Metas
QOR Admin将结合您的元配置,最新的配置将覆盖之前的配置。
user.Meta(&admin.Meta{Name: "Gender", Label: "Select Gender", Config: &admin.SelectOneConfig{Collection: []string{"Male", "Female", "Unknown"}}})
user.Meta(&admin.Meta{Name: "Gender", Config: &admin.SelectOneConfig{Collection: []string{"Male", "Female"}}})
// 变成
user.Meta(&admin.Meta{Name: "Gender", Label: "Select Gender", Config: &admin.SelectOneConfig{Collection: []string{"Male", "Female"}}})
注册Meta处理器
Meta处理将在每次重新配置Meta都被调用
genderMeta := user.Meta(&admin.Meta{Name: "Gender", Label: "Select Gender", Config: &admin.SelectOneConfig{Collection: []string{"Male", "Female"}}})
genderMeta.AddProcessor(*admin.MetaProcessor{
Name: "make-sure-label-is-select-gender",
Handler: func(meta *admin.Meta) {
meta.Label = "Select Gender"
},
})
创建新的Meta类型
QOR管理员只提供常见的元类型,您可以轻松创建自己的元类型,如:
user.Meta(&admin.Meta{Name: "FieldName", Type: "my-fancy-meta-type"})
然后创建模板meta/index/my-fancy-meta-type.tmpl,meta/show/my-fancy-meta-type.tmpl,然后将它们放入qor视图路径,即可完成。
meta/index/my-fancy-meta-type.tmpl将在呈现索引页面时使用,如果不存在,QOR Admin将使用Valuer中的meta值,并将其显示为列表中的字符串。
meta/form/my-fancy-meta-type.tmpl将在渲染显示/编辑页面时使用,该文件必须存在才能正确渲染meta。
QOR Slug有示例
创建Meta配置
如果要传递某些配置以供查看,Meta Config适合您,不同类型的Metas通常具有不同的配置内容,例如meta select one,您可以配置其数据源,打开类型,对于meta rich editor,您可以配置其使用的插件,资产管理器。
对于你创建的元类型,如果你需要将配置传递给视图,最好为它创建一个元配置,例如:
type FancyMetaConfig struct {
Config1 string
Config2 string
}
// 元配置必须实现这个接口
func (FancyMetaConfig) ConfigureQorMeta(metaor resource.Metaor) {
if meta, ok := metaor.(*admin.Meta); ok {
// do something for meta
}
}
Rich Editor Config可以看看示例
默认Meta配置器
Meta Configor是全局注册到Admin中的东西,以后注册的任何meta都会调用Meta Configor,例如:
// 如果未配置,所有`date`元数据将获得默认的FormattedValuer。
Admin.RegisterMetaConfigor("date", func(meta *Meta) {
if meta.FormattedValuer == nil {
meta.SetFormattedValuer(func(value interface{}, context *qor.Context) interface{} {
switch date := meta.GetValuer()(value, context).(type) {
case *time.Time:
if date == nil {
return ""
}
if date.IsZero() {
return ""
}
return utils.FormatTime(*date, "2006-01-02", context)
case time.Time:
if date.IsZero() {
return ""
}
return utils.FormatTime(date, "2006-01-02", context)
default:
return date
}
})
}
})
自定义视图
customize templates 跳转到视图一章去学习
注册FuncMap
注册功能映射到视图,然后你可以在你的模板中使用它们。
Admin.RegisterFuncMap("my_fancy_func", func() string {
return "my_fancy_func"
})
查看动作
如果您将任何模板放在{qor view path}/actions中,则会自动将其加载到索引/编辑/新建/显示页面。
您只能通过创建模板{qor view path}/actions/index/my_html_snippet.tmpl来为索引页面加载HTML代码段,该代码段将被加载到页面的subheader中。
QOR Activity, QOR Publish2是基于此策略构建的。
查看标题的操作
如果您将模板放入{qor view path} /actions/header中,它将被加载到管理网站的顶部区域,例如:
QOR Help,QOR Notification 是基于此实现的。
路由
您可以使用路由器定义自己的路由。
路由(也称为多路复用器,处理程序)是一种从URL路径映射到某些代码的方法,当最终用户访问该代码时,该代码就会执行。
注册HTTP路由
首先,获取 router:
router := Admin.GetRouter()
普通路由
router.Get("/path", func(context *admin.Context) {
// do something here
})
router.Post("/path", func(context *admin.Context) {
// do something here
})
router.Put("/path", func(context *admin.Context) {
// do something here
})
router.Delete("/path", func(context *admin.Context) {
// do something here
})
命名路由
router.Get("/path/:name", func(context *admin.Context) {
context.Request.URL.Query().Get(":name")
})
正则表达式支持
router.Get("/path/:name[world]", func(context *admin.Context) { // "/hello/world"
context.Request.URL.Query().Get(":name")
})
router.Get("/path/:name[\\d+]", func(context *admin.Context) { // "/hello/123"
context.Request.URL.Query().Get(":name")
})
中间件
QOR管理员的路由器有中间件支持,你可以用它做一些高级的工作,以下面的代码为例:
db1 := gorm.Open("sqlite", "db1.db")
db2 := gorm.Open("sqlite", "db2.db")
Admin.GetRouter().Use(&admin.Middleware{
Name: "switch_db",
Handler: func(context *admin.Context, middleware *admin.Middleware) {
// 将与产品相关的请求的管理员数据库切换到db2
if regexp.MustCompile("/admin/products").MatchString(context.Request.URL.Path) {
context.SetDB(db2)
}
middleware.Next(context)
},
})
与WEB框架集成
QOR Admin应该能够与大多数golang Web框架集成,以下是一些如何与它们集成的示例。
与HTTP ServeMux集成
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
http.ListenAndServe(":9000", mux)
与Beego整合
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
beego.Handler("/admin/*", mux)
beego.Run()
与Gin集成
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
r := gin.Default()
r.Any("/admin/*resources", gin.WrapH(mux))
r.Run()
与 gorilla/mux集成
adminMux := http.NewServeMux()
Admin.MountTo("/admin", adminMux)
r := mux.NewRouter()
r.PathPrefix("/admin").Handler(adminMux)
http.Handle("/", r)
部署到生产
部署应用程序时,许多人都喜欢二进制部署,它具有很多优点,这也是我们建议的部署应用程序的方式。
但是,正如您所知,在查找模板时,QOR Admin的默认AssetFS实现来自文件系统,这意味着这些模板必须存在于生产服务器上,否则将导致找不到模板错误。
我们构建QOR Bindatafs可以帮助它,它可以将QOR模板编译成二进制文件,请参阅其README以获得进一步的帮助。
缓存存储
缓存存储提供了一种方法来缓存web内容,以加快您的网站的速度。
Memcached缓存存储使用情况
import "github.com/qor/cache/memcached"
func main() {
client := memcached.New(&Config{Hosts: []string{"127.0.0.1:11211"}, NameSpace: "qor_demo_v1"})
// 保存值' Hello World '与键' Hello World '到缓存存储
err := client.Set("hello_world", "Hello World")
// 通过键“ hello_world”获取节省的值
result, err := client.Get("hello_world")
// 将user的编组后的值保存到缓存存储中
err := client.Set("user", user)
// 将保存的值解组到user2
err := client.Unmarshal("user", &user2)
//使用`hello_world`键获取保存的值; 如果密钥不存在,则返回的`func`结果将被保存到缓存存储区中
result, err := client.Fetch("hello_world", func() interface{} {
return "..."
})
// 删除保存的值
err := client.Delete(key string)
}
内存缓存存储使用情况
import "github.com/qor/cache/memory"
func main() {
client := memory.New()
// 与memcached缓存存储区使用相同的API
}
Redis缓存存储使用情况
import "github.com/qor/cache/redis"
func main() {
client = New(&redis.Options{Addr: "127.0.0.1:6379",
Password: "", // no password set
DB: 0, // use default DB
PoolSize: 100,
})
// 与memcached缓存存储区使用相同的API
}
BindataFS
BindataFS可以用来利用go-bindata将模板编译成二进制文件
安装BindataFS
go get -u -f github.com/qor/bindatafs/…
初始化项目的BindataFS,设置要存储BindataFS相关文件的路径,例如 config/bindatafs:
bindatafs config/bindatafs
用法
import "<your_project>/config/bindatafs"
func main() {
assetFS := bindatafs.AssetFS
// 将视图路径注册到AssetFS
assetFS.RegisterPath("<your_project>/app/views")
assetFS.RegisterPath("<your_project>/vender/plugin/views")
// 将注册视图路径下的模板编译为二进制
assetFS.Compile()
// 获取文件内容
fileContent, ok := assetFS.Asset("home/index.tmpl")
}
您需要在运行go build之前使用Compile方法将模板编译到go文件中,并且如果更改了任何模板,则需要重新编译它。
如果您使用标签bindatafs启动应用程序,AssetFS将访问生成的go文件或当前二进制文件中的文件
go run -tags ‘bindatafs’ main.go
否则它将从文件系统的已注册视图路径访问其内容,这对于本地开发更容易
go run main.go
使用命名空间
尽管您可以启动多个assetfs软件包来容纳来自不同视图路径的模板(模板名称可能有冲突),以用于不同的用例,但是Bindatafs为您提供了一个更简单的解决方案。
func main() {
// 生成具有名称空间的子AssetFS
adminAssetFS := assetFS.NameSpace("admin_related_files")
// 将视图路径注册到这个名称空间
adminAssetFS.RegisterPath("<your_project>/app/admin_views")
// 访问注册下的文件,查看当前名称空间的路径
adminAssetFS.Asset("admin_view.tmpl")
}
QOR Admin 使用BindataFS
import "<your_project>/config/bindatafs"
func main() {
Admin = admin.New(&qor.Config{DB: db.Publish.DraftDB()})
Admin.SetAssetFS(bindatafs.AssetFS.NameSpace("admin"))
}
QOR Render 使用BindataFS
import "github.com/qor/render"
func main() {
View := render.New()
View.SetAssetFS(assetFS.NameSpace("views"))
}
QOR Widget 使用BindataFS
import "github.com/qor/widget"
func main() {
Widgets := widget.New(&widget.Config{DB: db.DB})
Widgets.SetAssetFS(assetFS.NameSpace("widgets"))
}
静态文件使用BindataFS
func main() {
mux := http.NewServeMux()
// 这将把public下的所有文件添加到一个生成的go文件中,该文件将包含在二进制文件中
assetFS := assetFS.FileServer(http.Dir("public"))
// 如果你只想包含指定的路径,你可以像这样使用它
assetFS := assetFS.FileServer(http.Dir("public"), "javascripts", "stylesheets", "images")
for _, path := range []string{"javascripts", "stylesheets", "images"} {
mux.Handle(fmt.Sprintf("/%s/", path), assetFS)
}
}
I18n
I18n为您的应用程序提供国际化支持,它支持2种存储(后端),数据库和文件系统。
用法
使用存储模式初始化I18n。 您可以同时使用两个存储,较早的存储具有更高的优先级。 因此,在此示例中,I18n将首先在数据库中查找翻译,如果找不到,则继续在YAML文件中查找它。
import (
"github.com/jinzhu/gorm"
"github.com/qor/i18n"
"github.com/qor/i18n/backends/database"
"github.com/qor/i18n/backends/yaml"
)
func main() {
db, _ := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
I18n := i18n.New(
database.New(&db), // 从数据库加载翻译
yaml.New(filepath.Join(config.Root, "config/locales")), // 从YAML文件的config/locales目录中加载翻译
)
I18n.T("en-US", "demo.greeting") // 一开始不存在
I18n.T("en-US", "demo.hello") // 存在于yml文件中
}
一旦为I18n设置了数据库,则在编译应用程序时,I18n.T()中所有未翻译的翻译将被加载到数据库的translations表中。 例如,在示例中我们有一个未翻译的I18n.T(“ en-US”,“ demo.greeting”),因此I18n编译后将在翻译表中生成此记录。
local | key | value |
---|---|---|
en-US | demo.greeting |
YAML文件格式为
en-US:
demo:
hello: "Hello, world"
使用内置界面通过QOR Admin进行翻译管理
I18n有一个内置的web翻译界面,它与QOR管理集成在一起。
Admin.AddResource(I18n)
然后会在QOR管理界面添加这样一个页面,参考在线演示。
在Golang模板中使用
在模板中使用I18n的简单方法是定义一个t函数并将其注册为FuncMap:
func T(key string, value string, args ...interface{}) string {
return I18n.Default(value).T("en-US", key, args...)
}
// 然后在模板中使用它
{{ t "demo.greet" "Hello, {{$1}}" "John" }} // -> Hello, John
内置的翻译管理功能
I18n具有直接管理翻译的功能。
// 添加翻译
I18n.AddTranslation(&i18n.Translation{Key: "hello-world", Locale: "en-US", Value: "hello world"})
// 更新翻译
I18n.SaveTranslation(&i18n.Translation{Key: "hello-world", Locale: "en-US", Value: "Hello World"})
// 删除翻译
I18n.DeleteTranslation(&i18n.Translation{Key: "hello-world", Locale: "en-US", Value: "Hello World"})
范围和默认值
调用翻译范围或设置默认值
// 阅读带有Scope的翻译
I18n.Scope("home-page").T("zh-CN", "hello-world") // read translation with translation key `home-page.hello-world`
// 阅读带有“Default Value”的翻译
I18n.Default("Default Value").T("zh-CN", "non-existing-key") // Will return default value `Default Value`
回调
I18n有回调功能。
i18n := New(&backend{})
i18n.AddTranslation(&Translation{Key: "hello-world", Locale: "en-GB", Value: "Hello World"})
fmt.Print(i18n.Fallbacks("en-GB").T("zh-CN", "hello-world")) // "Hello World"
要全局设置fallback Locale),可以使用I18n.FallbackLocales。 此函数接受map[string] []string作为参数。 关键是回退语言环境,[]字符串是可以回退到第一个语言环境的语言环境。
I18n.FallbackLocales = map[string][]string{"en-GB": []{"fr-FR", "de-DE", "zh-CN"}}
插值
I18n使用Golang模板来解析带有插值变量的翻译。
type User struct {
Name string
}
I18n.AddTranslation(&i18n.Translation{Key: "hello", Locale: "en-US", Value: "Hello {{.Name}}"})
I18n.T("en-US", "hello", User{Name: "Jinzhu"}) //=> Hello Jinzhu
多元化
I18n利用cldr实现多元化,为此目的,它提供了功能p,zero,one,two,few,many,other。 请参考cldr文档以获取更多信息。
I18n.AddTranslation(&i18n.Translation{Key: "count", Locale: "en-US", Value: "{{p "Count" (one "{{.Count}} item") (other "{{.Count}} items")}}"})
I18n.T("en-US", "count", map[string]int{"Count": 1}) //=> 1 item
有序参数
I18n.AddTranslation(&i18n.Translation{Key: "ordered_params", Locale: "en-US", Value: "{{$1}} {{$2}} {{$1}}"})
I18n.T("en-US", "ordered_params", "string1", "string2") //=> string1 string2 string1
内联编辑
在将翻译的数据注册到QOR Admin后,您可以使用QOR Admin界面(UI)管理翻译的数据,不过,我们要提醒你的是,在不了解上下文的情况下翻译一篇译文是非常困难的(而且很容易出错!)。幸运的是, 开发了QOR Admin的内联编辑功能来解决此问题!
内联编辑允许管理员从前端管理翻译。 类似于Golang模板集成,您需要为Golang模板注册功能映射以呈现内联可编辑翻译。
好消息是我们创建了一个包让您轻松执行此操作,它将生成一个FuncMap,您只需要在解析模板时使用它:
// `I18n` hold translations backends
// `en-US` current locale
// `true` enable inline edit mode or not, if inline edit not enabled, it works just like the funcmap in section "Integrate with Golang Templates"
inline_edit.FuncMap(I18n, "en-US", true) // => map[string]interface{}{
// "t": func(string, ...interface{}) template.HTML {
// // ...
// },
// }
L10n
L10n使您的GORM模型能够针对不同的语言环境进行本地化。
L10n利用GORM回调来处理本地化,因此您需要先注册回调:
import (
"github.com/jinzhu/gorm"
"github.com/qor/l10n"
)
func main() {
db, err := gorm.Open("sqlite3", "demo_db")
l10n.RegisterCallbacks(&db)
}
模型本地化
将l10n.Locale作为匿名字段嵌入到模型中以启用本地化,例如,在一个以产品管理为重点的假设项目中:
type Product struct {
gorm.Model
Name string
Code string
l10n.Locale
}
l10n.Locale将使用GORM的AutoMigrate创建字段,将language_code列添加为具有现有主键的复合主键。
language_code列将用于保存本地化模型的语言环境。 如果未设置区域设置,则将使用全局默认区域设置(en-US)。 您可以通过设置l10n.Global来覆盖全局默认语言环境,例如:
l10n.Global = ‘zh-CN’
从全局product创建本地化资源
// 创建全局product
product := Product{Name: "Global product", Description: "Global product description"}
DB.Create(&product)
product.LanguageCode // "en-US"
// Create zh-CN product
product.Name = "中文产品"
DB.Set("l10n:locale", "zh-CN").Create(&product)
// Query zh-CN product with primary key 111
DB.Set("l10n:locale", "zh-CN").First(&productCN, 111)
productCN.Name // "中文产品"
productCN.LanguageCode // "zh"
直接创建本地化资源
默认情况下,仅允许创建全局数据,本地数据必须从全局数据本地化。
如果要允许用户直接创建本地化数据,则可以为模型/结构嵌入l10n.LocaleCreatable,例如:
type Product struct {
gorm.Model
Name string
Code string
l10n.LocaleCreatable
}
保持本地化资源的字段同步
将标签l10n:“sync”添加到您希望始终与全局记录同步的字段中
type Product struct {
gorm.Model
Name string
Code string `l10n:"sync"`
l10n.Locale
}
现在,本地化产品的代码将与全局产品的代码相同。 该代码不受本地化资源的影响,当全局记录更改其代码时,本地化记录的代码将自动同步。
查询方式
L10n提供了5种查询模式。
全局-查找所有全局记录,
语言环境-查找本地化的记录,
反向-查找尚未本地化的全局记录,
不受范围限制-原始查询,查询时不会自动添加语言环境条件,
默认值-查找本地化记录,如果找不到,则返回全局记录。
您可以通过以下方式指定模式:
dbCN := db.Set("l10n:locale", "zh-CN")
mode := "global"
dbCN.Set("l10n:mode", mode).First(&product, 111)
// SELECT * FROM products WHERE id = 111 AND language_code = 'en-US';
mode := "locale"
db.Set("l10n:mode", mode).First(&product, 111)
// SELECT * FROM products WHERE id = 111 AND language_code = 'zh-CN';
Qor集成
尽管L10n可以单独使用,但可以与QOR很好地集成。
默认情况下,QOR仅允许您管理全局语言。 如果已配置身份验证,则QOR管理员将尝试从当前用户获取允许的语言环境。
可查看地区——当前用户具有读权限的地区:
func (user User) ViewableLocales() []string {
return []string{l10n.Global, "zh-CN", "JP", "EN", "DE"}
}
可编辑区域设置-当前用户对其具有管理(创建/更新/删除)权限的区域设置:
func (user User) EditableLocales() []string {
if user.role == "global_admin" {
return []string{l10n.Global, "zh-CN", "EN"}
} else {
return []string{"zh-CN", "EN"}
}
}
渲染
用法
初始化
func main() {
Render := render.New(&render.Config{
ViewPaths: []string{"app/new_view_path"},
DefaultLayout: "application", // default value is application
FuncMapMaker: func(*Render, *http.Request, http.ResponseWriter) template.FuncMap {
// genereate FuncMap that could be used when render template based on request info
},
})
}
接下来,调用Execute函数来渲染模板…
Render.Execute("index", context, request, writer)
Execute函数接受4个参数:
1 模板名称。 在此示例中,Render将从视图路径中查找模板index.tmpl。 默认视图路径为{current_repo_path}/app/views,您可以注册更多视图路径。
2 您可以在模板中使用上下文,它是一个接口{},您可以在视图中使用它。 例如,如果您传递context [“ CurrentUserName”] =“ Memememe”作为上下文。 在模板中,您可以调用{{.CurrentUserName}}来获取值“ Memememe”。
3 http.Request
4 http.ResponseWriter
理解 yield
yield是可以在布局视图中使用的功能,它将呈现当前指定的模板。 对于上面的示例,将yield视为占位符,它将被模板index.tmpl的内容代替。
<!-- app/views/layout/application.tmpl -->
<html>
<head>
</head>
<body>
{{yield}}
</body>
</html>
指定布局
默认布局为{current_repo_path}/app/views/layouts/application.tmpl。 如果要使用其他布局,例如new_layout,则可以将其作为参数传递给Layout函数。
Render.Layout("new_layout").Execute("index", context, request, writer)
渲染器将在{current_repo_path} /app/views/layouts/new_layout.tmpl中找到布局。
使用辅助功能进行渲染
有时,您可能希望模板中包含一些辅助功能。 渲染器支持通过Funcs函数传递辅助函数。
Render.Funcs(funcsMap).Execute("index", obj, request, writer)
funcsMap基于html/template.FuncMap。 所以用
funcMap := template.FuncMap{
"Greet": func(name string) string { return "Hello " + name },
}
您可以在模板中调用它
{% raw %}{{Greet "Memememe" }}{% endraw %}
与Responder一起使用
像这样把渲染放在Responder handle函数中。
func handler(writer http.ResponseWriter, request *http.Request) {
responder.With("html", func() {
Render.Execute("demo/index", viewContext, *http.Request, http.ResponseWriter)
}).With([]string{"json", "xml"}, func() {
writer.Write([]byte("this is a json or xml request"))
}).Respond(request)
})
与Bindatafs一起使用
$ bindatafs --exit-after-compile=false config/views
func main() {
Render := render.New()
Render.SetAssetFS(views.AssetFS)
Render.Execute("index", context, request, writer)
}
响应器 Responder
注册mime类型
import "github.com/qor/responder"
responder.Register("text/html", "html")
responder.Register("application/json", "json")
responder.Register("application/xml", "xml")
默认情况下,响应者具有上述3种mime类型注册。 您可以使用Register函数注册更多类型,该函数接受2个参数:
mime类型, 类似 text/html
mime类型的格式, 类似 html
响应注册mime类型
func handler(writer http.ResponseWriter, request *http.Request) {
responder.With("html", func() {
writer.Write([]byte("this is a html request"))
}).With([]string{"json", "xml"}, func() {
writer.Write([]byte("this is a json or xml request"))
}).Respond(request)
})
如果Responder无法找到相应的mime类型,则示例中的第一个html将是默认的响应类型。
邮件程序
初始化邮件程序
Mailer将支持多个发件人适配器,其工作原理类似,您需要先初始化Mailer,然后使用它来发送电子邮件。
这是使用gomail发送电子邮件的方法
import (
"github.com/qor/mailer"
"github.com/qor/mailer/gomailer"
gomail "gopkg.in/gomail.v2"
)
func main() {
// 配置 gomail
dailer := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
sender, err := dailer.Dial()
// 初始化邮件程序
Mailer := mailer.New(&mailer.Config{
Sender: gomailer.New(&gomailer.Config{Sender: sender}),
})
}
发送邮件
import "net/mail"
func main() {
Mailer.Send(mailer.Email{
TO: []mail.Address{{Address: "jinzhu@example.org", Name: "jinzhu"}},
From: &mail.Address{Address: "jinzhu@example.org"},
Subject: "subject",
Text: "text email",
HTML: "html email <img src='cid:logo.png'/>",
Attachments: []mailer.Attachment{{FileName: "gomail.go"}, {FileName: "../test/logo.png", Inline: true}},
})
}
使用模板发送电子邮件
Mailer正在使用Render呈现电子邮件模板和布局,请参考How-To。
电子邮件可以具有HTML和文本版本,在发送电子邮件时,
它将从视图路径中查找模板hello.html.tmpl和布局application.html.tmpl,并将其呈现为HTML版本的内容,并使用模板hello.text.tmpl和布局application.text.tmpl作为文本版本的内容。
如果找不到布局文件,则仅将模板作为内容呈现,如果找不到模板,则将跳过该版本,例如,如果hello.text.tmpl不存在,则我们 只会发送HTML版本。
Mailer.Send(
mailer.Email{
TO: []mail.Address{{Address: Config.DefaultTo}},
From: &mail.Address{Address: Config.DefaultFrom},
Subject: "hello",
},
mailer.Template{Name: "hello", Layout: "application", Data: currentUser},
)
邮件视图路径
所有模板和布局应位于app/views/mailers中,但是您可以通过自定义邮件程序的AssetFS来更改或注册更多路径。
import "github.com/qor/assetfs"
func main() {
assetFS := assetfs.AssetFS().NameSpace("mailer")
assetFS.RegisterPath("mailers/views")
Mailer := mailer.New(&mailer.Config{
Sender: gomailer.New(&gomailer.Config{Sender: sender}),
AssetFS: assetFS,
})
}
Sessions
QOR的会话管理
它将其他库(如SCS,Gorilla Session)包装到一个公共接口中,该接口将用于QOR库和您的应用程序。
基本用法
import (
"github.com/gorilla/sessions"
"github.com/qor/session/gorilla"
// "github.com/alexedwards/scs/engine/memstore"
)
var SessionManager = session.ManagerInterface
func main() {
// Use gorilla session as the backend
engine := sessions.NewCookieStore([]byte("something-very-secret"))
SessionManager = gorilla.New("_session", engine)
// Use SCS as the backend
// engine := memstore.New(0)
// SessionManager := scs.New(engine)
mux := http.NewServeMux()
mux.HandleFunc("/put", putHandler)
mux.HandleFunc("/get", getHandler)
// Your routes
// Wrap your application's handlers or router with session manager's middleware
http.ListenAndServe(":7000", manager.Middleware(mux))
}
func putHandler(w http.ResponseWriter, req *http.Request) {
// Store a key and associated value into session data
SessionManager.Add(w, req, "key", "value")
}
func getHandler(w http.ResponseWriter, req *http.Request) {
// Get saved session data with key
value := SessionManager.Get(req, "key")
io.WriteString(w, value)
}
会话管理器的接口
type ManagerInterface interface {
// Add value to session data, if value is not string, will marshal it into JSON encoding and save it into session data.
Add(w http.ResponseWriter, req *http.Request, key string, value interface{}) error
// Get value from session data
Get(req *http.Request, key string) string
// Pop value from session data
Pop(w http.ResponseWriter, req *http.Request, key string) string
// Flash add flash message to session data
Flash(w http.ResponseWriter, req *http.Request, message Message) error
// Flashes returns a slice of flash messages from session data
Flashes(w http.ResponseWriter, req *http.Request) []Message
// Load get value from session data and unmarshal it into result
Load(req *http.Request, key string, result interface{}) error
// PopLoad pop value from session data and unmarshal it into result
PopLoad(w http.ResponseWriter, req *http.Request, key string, result interface{}) error
// Middleware returns a new session manager middleware instance.
Middleware(http.Handler) http.Handler
}
QOR集成
我们在github.com/qor/session/manager包中创建了一个默认的会话管理器,该默认的会话管理器在某些QOR库中使用,例如QOR Admin,默认的QOR Auth来管理会话,闪存消息。
定义如下:
var SessionManager session.ManagerInterface = gorilla.New("_session", sessions.NewCookieStore([]byte("secret")))
您应该将其更改为您自己的会话存储或使用您自己的密码。
import (
"github.com/qor/session/manager"
)
func main() {
// Overwrite session manager
engine := sessions.NewCookieStore([]byte("your-own-secret-code"))
manager.SessionManager = gorilla.New("_gorilla_session", engine)
}
Worker
Worker在后台运行单个作业,它可以立即执行或在预定时间执行。
一旦向QOR管理员注册,Worker将在导航树中提供一个Workers部分,其中包含列出和管理Worker的以下方面的页面:
所有的工作。
正在运行的作业:当前正在运行的作业。
已计划在未来某个时间运行的作业。
完成:完成工作。
Errors:任何已经运行的Workers输出的任何错误。
可调度作业的管理界面将具有额外的调度时间输入,管理员可以使用该输入设置计划的日期和时间。
用法
import "github.com/qor/worker"
func main() {
// Define Worker
Worker := worker.New()
// Arguments used to run a job
type sendNewsletterArgument struct {
Subject string
Content string `sql:"size:65532"`
SendPassword string
// If job's argument has `worker.Schedule` embedded, it will get run at a scheduled time
worker.Schedule
}
// Register Job
Worker.RegisterJob(&worker.Job{
Name: "Send Newsletter", // Registerd Job Name
Handler: func(argument interface{}, qorJob worker.QorJobInterface) error {
// `AddLog` add job log
qorJob.AddLog("Started sending newsletters...")
qorJob.AddLog(fmt.Sprintf("Argument: %+v", argument.(*sendNewsletterArgument)))
for i := 1; i <= 100; i++ {
time.Sleep(100 * time.Millisecond)
qorJob.AddLog(fmt.Sprintf("Sending newsletter %v...", i))
// `SetProgress` set job progress percent, from 0 - 100
qorJob.SetProgress(uint(i))
}
qorJob.AddLog("Finished send newsletters")
return nil
},
// Arguments used to run a job
Resource: Admin.NewResource(&sendNewsletterArgument{}),
})
// Add Worker to qor admin, so you could manage jobs in the admin interface
Admin.AddResource(Worker)
}
注意事项
如果一个作业在当前时间的2分钟内被调度,那么它将立即运行。
通过管理界面,可以中止当前正在运行的作业
通过管理界面,可以中止计划的作业
通过管理界面,可以更新计划的作业,包括设置新的日期和时间
我是被电商系统这个词吸引来的,但读完官方文档,没觉得与电商系统有啥关系。后台倒是感觉会比较简单,整个搭建也是毕竟快速。
接下来通过示例进行学习,网上它的资料确实少了点。