QOR: Golang开发的电商系统和CMS工具库--读官方文档
QOR: Golang开发的电商系统和CMS工具库--示例学习
基于Go语言开发的电商系统和CMS的SDK,根据网友的说法:QOR可以看作是PHP中的ThinkPHP,Python中的Django。
网友专栏介绍学习,不过看文章是2018年的了。相关的文章有限,估计又得啃外文学习。
https://github.com/golangpkg/qor-cms-demos 各章示例代码,虽然也是3年前的。
之前大致学习了一下官方文档,理解还是不够,需要结合更多的实例进行学习。
根据阅读官方文档,并没有发现什么与电商系统相关的内容。
在 auth 一节,按作者代码,并没出现登陆和注册内容,只有链接,不知道是不是views的问题。
数据库的操作就是增删改查CURD。
package main
import (
"database/sql"
"fmt"
"net/http"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/qor/admin"
)
//声明全局变量
var (
// 注册数据库,可以是sqlite3 也可以是 mysql 换下驱动就可以了。
DB, _ = gorm.Open("sqlite3", "demo.db")
// 初始化admin 还有其他的,比如API
Admin = admin.New(&admin.AdminConfig{DB: DB, SiteName: "Qor Admin "})
)
func init() {
//地址
type Address struct {
gorm.Model
Code string
Name string
}
//角色
type Role struct {
gorm.Model
Name string
}
//用户
type User struct {
gorm.Model
Email string
Password string
Name sql.NullString
Gender string
Role Role
Addresses []Address
Active bool
}
DB.AutoMigrate(&Address{}, &Role{}, &User{}) //自动创建表。
address := Admin.AddResource(&Address{}, &admin.Config{Name: "地址管理", Menu: []string{"组织管理"}})
role := Admin.AddResource(&Role{}, &admin.Config{Name: "角色管理", Menu: []string{"组织管理"}})
// Add it to Admin
user := Admin.AddResource(&User{}, &admin.Config{Name: "用户管理", Menu: []string{"组织管理"}})
// 显示字段
//user.IndexAttrs("Name", "Gender", "Role", "Password", "CreateAt")
// 不显示字段
user.IndexAttrs("-Password", "-Role", "-Addresses")
user.Meta(&admin.Meta{Name: "Email", Label: "邮箱"})
user.Meta(&admin.Meta{Name: "Name", Label: "名称"})
user.Meta(&admin.Meta{Name: "Gender", Label: "性别", Config: &admin.SelectOneConfig{Collection: []string{"Male", "Female", "Unknown"}}})
// 分组显示
//user.Scope(&admin.Scope{Name: "Male", Group: "Gender", Handler: func(db *gorm.DB, context *qor.Context) *gorm.DB {
// return db.Where("gender = ?", "Male")
//}})
//user.Scope(&admin.Scope{Name: "Female", Group: "Gender", Handler: func(db *gorm.DB, context *qor.Context) *gorm.DB {
// return db.Where("gender = ?", "Female")
//}})
user.Action(&admin.Action{
Name: "enable",
Handler: 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.(*User)).Update("Active", true)
}
return nil
},
})
// Filter users by gender
user.Filter(&admin.Filter{
Name: "性别",
Config: &admin.SelectOneConfig{
Collection: []string{"Male", "Female", "Unknown"},
},
})
// Filter products by collection
user.Filter(&admin.Filter{
Name: "角色",
Config: &admin.SelectOneConfig{RemoteDataResource: role},
})
fmt.Println(user)
fmt.Println(role)
fmt.Println(address)
}
func main() {
// 启动服务
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
http.ListenAndServe(":9000", mux)
}
通过admin.Config添加了后台菜单项
通过user.Meta修改了用户字段中文名称
通过user.Filter增加了过滤用户的相关属性(这里截图看不出来)
关于API
默认qor启动admin之后有了api的接口了。
package main
import (
"database/sql"
"fmt"
"net/http"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/qor/admin"
"github.com/qor/qor"
)
//声明全局变量
var (
// 注册数据库,可以是sqlite3 也可以是 mysql 换下驱动就可以了。
DB, _ = gorm.Open("sqlite3", "demo.db")
// 初始化admin 还有其他的,比如API
API = admin.New(&qor.Config{DB: DB})
)
func init() {
//用户
type User struct {
gorm.Model
Email string
Password string
Name sql.NullString
Gender string
Active bool
}
DB.AutoMigrate(&User{}) //自动创建表。
// Add it to Admin
user := API.AddResource(&User{})
user.Action(&admin.Action{
Name: "enable",
Handler: 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.(User)).Update("Active", true)
}
return nil
},
})
fmt.Println(user)
}
func main() {
// 启动服务
mux := http.NewServeMux()
API.MountTo("/api/v1", mux)
http.ListenAndServe(":9000", mux)
}
GET http://localhost:9000/api/v1/users.json 以json格式返回用户列表
GET /api/v1/users/1.json - 获得第一个数据
POST /api/v1/users - 创建
PUT /api/v1/users/12 - 更新
DELETE /api/v1/users/12 - 删除
GET http://localhost:9000/api/v1/users.xml 以xml格式返回用户列表
i18n
看着一屏的英文是头痛的,使用i18n。官方已经提l10n了。
运行qor-example
-
go get -u github.com/qor/qor-example
-
只是示例而已,将数据库改为sqlite,在config/database.yml(此文件复制database.example.yml示例得来)
db:
adapter: sqlite
name: qor_example
user: root (这个实际就不需要了,随便给内容)
-
生成一些随机数据 go run config/db/seeds/main.go config/db/seeds/seeds.go
-
go run main.go 即可看到效果
github.com/golangpkg/qor-cms 示例学习
注:代码均为节选
- main.go
//开启session。
beego.BConfig.WebConfig.Session.SessionOn = true
//DB, _ := gorm.Open("sqlite3", "demo.db")
//db库也只是根据配置文件,自动连接数据库。在测试中,我使用了以上的sqlite3
DB := db.DB
//根据表的定义,自动生成数据表
DB.AutoMigrate(&models.Category{}, &models.Article{}, &auth_identity.AuthIdentity{},
&models.IndexSlider{}, &models.JobCompany{}, &models.Job{}, &models.Coin100rank{})
// 后台管理相关初始化设置
Admin := admin.New(&admin.AdminConfig{SiteName: "qor-cms", DB: DB, Auth: auth.AdminAuth{}})
//初始化文章管理
models.InitArticleUi(Admin)
//初始化首页轮播图
models.InitIndexSliderUi(Admin)
// 启动服务。这里它与beego集成使用。
mux := http.NewServeMux()
Admin.MountTo("/admin", mux)
beego.Handler("/admin/*", mux)
beego.Run()
- router.go
userInfoController := &controllers.UserInfoController{}
beego.Router("/api/publish5page", &controllers.MainController{}, "get:GetApiPublish") //api调用
beego.Router("/", userInfoController, "get:LoginIndex") //打开首页就要求登陆
beego.Router("/auth/login", userInfoController, "get:LoginIndex")
beego.Router("/auth/login", userInfoController, "post:Login")
beego.Router("/auth/logout", userInfoController, "get:Logout")
beego.Router("/admin/common/kindeditor/upload", &controllers.FileUploadController{}, "post:Upload") //上传功能
- UserInfoController
在用户登陆处,使用另外一种保存session的方法。而非beego的自带方式。这个暂时没明白。
//c.SetSession(USER_SESSION_NAME, userName)
manager.SessionManager.Add(c.Ctx.ResponseWriter, c.Ctx.Request, USER_SESSION_NAME, userName)
// 登入
func (c *UserInfoController) Login() {
userName := c.GetString("UserName", "")
password := c.GetString("Password", "")
//获得sessionid。
if c.CruSession == nil {
c.StartSession()
}
sessionId := c.CruSession.SessionID()
logs.Info("sessionId %s get userName %s and password %s", sessionId, userName, password)
if userName == "" {
userName = "no_user_name"
}
c.SetSession("UserName", userName)
if userName != "admin" || password != adminPassword {
logs.Info("##################### login Error #####################")
c.Ctx.Redirect(302, "/auth/login?err=password")
return
}
//将用户对象放到session里面。
//c.SetSession(USER_SESSION_NAME, userName)
manager.SessionManager.Add(c.Ctx.ResponseWriter, c.Ctx.Request, USER_SESSION_NAME, userName)
c.Ctx.Redirect(302, "/admin")
return
}
//登出
func (c *UserInfoController) Logout() {
if c.CruSession == nil {
c.StartSession()
}
sessionId := c.CruSession.SessionID()
logs.Info("==sessionId %s ==", sessionId)
//设置 SessionDomain 名称。
SessionDomain := beego.AppConfig.String("SessionDomain")
//SetSecureCookie将值编码后,将值放入cookie中。(这里是作清除)
c.SetSecureCookie(cookieSecret, cookieName, "", 0, "/", SessionDomain)
c.DestroySession()
c.Ctx.Redirect(302, "/auth/login")
return
}
- 初始化文章管理 InitArticleUi
想看看它如何实现的引入新的Editor
定义文章表结构,必须gorm.Model用于qor管理。其它没有什么特别
type Article struct {
gorm.Model
//Id int64 `orm:"auto"`
Title string //标题
ImgUrl string //文章图片
Content string `gorm:"type:text"` //内容
Description string `gorm:"type:text"` //内容描述
Category Category
CategoryId int64 `form:"category"` //分类
Url string //地址
IsPublish bool //是否发布。
//publish2.Schedule
}
初始化内容
func InitArticleUi(adminConf *admin.Admin) {
// Create resources from GORM-backend model
//文章分类(创建文章及类管理的菜单)
category := adminConf.AddResource(&Category{}, &admin.Config{Name: "分类管理", Menu: []string{"资源管理"}})
category.Meta(&admin.Meta{Name: "Name", Label: "名称"})
//PageCount: 5,
article := adminConf.AddResource(&Article{}, &admin.Config{Name: "文章管理", Menu: []string{"资源管理"}})
//标识字段对应中文名及数据类型
article.Meta(&admin.Meta{Name: "Title", Label: "标题", Type: "text"})
article.Meta(&admin.Meta{Name: "ImgUrl", Label: "图片", Type: "kindimage"}) //不知道这个类型和下方的类型在内部如何匹配?
article.Meta(&admin.Meta{Name: "Content", Label: "内容", Type: "kindeditor"})
article.Meta(&admin.Meta{Name: "Category", Label: "分类"})
article.Meta(&admin.Meta{Name: "CreatedAt", Label: "创建时间"})
article.Meta(&admin.Meta{Name: "UpdatedAt", Label: "更新时间"})
article.Meta(&admin.Meta{Name: "Url", Label: "地址", Type: "readonly"})
article.Meta(&admin.Meta{Name: "IsPublish", Label: "是否发布", Type: "checkbox"})
article.IndexAttrs("Title", "Category", "IsPublish", "Url", "ImgUrl", "CreatedAt", "UpdatedAt") //列表时只显示这些
article.NewAttrs("Title", "Url", "IsPublish", "Category", "ImgUrl", "Content") //新增时显示这些字段
article.EditAttrs("Title", "Url", "IsPublish", "Category", "ImgUrl", "Content") //编辑时显示的字段
//增加发布功能:发布按钮,显示到右侧上面。
article.Action(&admin.Action{
Name: "publishAll",
Label: "全部发布",
Handler: func(actionArgument *admin.ActionArgument) error {
logs.Info("############### publishAll ###############")
GenArticleAndCategoryList(0) //生成html代码
return nil
},
Modes: []string{"collection"},
})
// 发布按钮,显示到右侧上面。
article.Action(&admin.Action{
Name: "publish5page",
Label: "增量发布5页",
Handler: func(actionArgument *admin.ActionArgument) error {
logs.Info("############### publish5page ###############")
GenArticleAndCategoryList(5) //生成html代码。
return nil
},
Modes: []string{"collection"},
})
article.AddProcessor(&resource.Processor{
Name: "process_store_data", // register another processor with
Handler: func(value interface{}, metaValues *resource.MetaValues, context *qor.Context) error {
if article, ok := value.(*Article); ok {
// do something...
logs.Info("################ article ##################")
// 生成Url
if article.Url == "" {
t := article.CreatedAt //time.Now()
if t.IsZero() { //如果创建事件为空。
t = time.Now()
}
url := fmt.Sprintf("%d-%02d/%d.html", t.Year(), t.Month(), t.Unix())
logs.Info(t, url)
article.Url = url
}
//自动更新/生成摘要。新建,修改都更新。
if article.Content != "" {
//如果摘要为空,且内容不为空。
//去除所有尖括号内的HTML代码,并换成换行符
re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
newContent := re.ReplaceAllString(article.Content, "\n")
//去除连续的换行符
re, _ = regexp.Compile("\\s{2,}")
newContent = re.ReplaceAllString(newContent, "\n")
newContent = strings.TrimSpace(newContent)
newContentRune := []rune(newContent)
if len(newContentRune) > 75 {
article.Description = string(newContentRune[0:75])
} else {
article.Description = newContent
}
logs.Info("description: ", article.Description)
}
}
return nil
},
})
}