(原) QOR: Golang开发的电商系统和CMS工具库--示例学习(未完待续)

原创文章,请后转载,并注明出处。

QOR: Golang开发的电商系统和CMS工具库--读官方文档

QOR: Golang开发的电商系统和CMS工具库--示例学习


基于Go语言开发的电商系统和CMS的SDK,根据网友的说法:QOR可以看作是PHP中的ThinkPHP,Python中的Django。

Github

官方文档 https://doc.getqor.com/

官网 https://getqor.com

网友专栏介绍学习,不过看文章是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

  1. go get -u github.com/qor/qor-example

  2. 只是示例而已,将数据库改为sqlite,在config/database.yml(此文件复制database.example.yml示例得来)

db:
  adapter: sqlite
  name: qor_example
  user: root  (这个实际就不需要了,随便给内容)
  1. 生成一些随机数据 go run config/db/seeds/main.go config/db/seeds/seeds.go

  2. go run main.go 即可看到效果


github.com/golangpkg/qor-cms 示例学习

注:代码均为节选

  1. 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()
  1. 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")  //上传功能
  1. 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
}
  1. 初始化文章管理 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
		},
	})
}

相关文章