(原) QOR5学习

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

之前学习过QOR,熟悉以后可以快速的构建后台。今天看它悄悄更新了,名为QOR5,学起来。

官网如前
文档亦如前,不过看起来示例多了。
Github发现的人不多。

1分钟感受

官方文档有一篇1分钟快速入门,硬是多研究了一下才成功运行。

安装QOR5示例构建工具

go install github.com/qor5/docs/cmd/qor5@latest

运行QOR5构建

qor5
提示输入包名等,按提示选择或输入

进入目录运行
  1. 用 docker-compose up 启动一个postgresql数据库docker
  2. 运行 source dev_env ,通过环境变量设置相关
  3. go run main.go 启动环境
改错

构建的工程居然有错,得自己改改。关键都是新手上路,咋改呢。
admin/config.go文件中

	pm := pageBuilder.Configure(b, db, l10nBuilder, ab)
	tm := pageBuilder.ConfigTemplate(b, db)
	cm := pageBuilder.ConfigCategory(b, db)
改为
	pm := pageBuilder.Configure(b, db, l10nBuilder, ab, nil, nil)
	tm := pageBuilder.ConfigTemplate(b, db)
	cm := pageBuilder.ConfigCategory(b, db, nil)

它想要的和我给的不一样,没有的就nil吧。居然运行起来。

默认使用了postgresql,而我更喜欢默认使用sqlite。普通的应用,sqlite足。

看起来示例不少,应该能愉快的玩耍了。


将admin/init_sql.go稍做修改,用于适配sqlite。已在sqlite下正常运行。

另外,在admin/config.go中,添加 b.BrandTitle(“学习系统”) 会出错,现象是浏览器刷新不出来。硬是把它改成了b.BrandTitle(“AI学习系统”)才正常。看起来不能直接给汉字?必须要字符先?

进一步跟踪发现,在github.com/qor5/admin/presets/presets.go中,行921: pr.PageTitle = fmt.Sprintf("%s - %s", innerPr.PageTitle, i18n.T(ctx.R, ModelsI18nModuleKey, b.brandTitle))
如果直接改为pr.PageTitle = fmt.Sprintf("%s - %s", innerPr.PageTitle, b.brandTitle) 则不存在此问题。看起来是i18n在查询对应的词时,找不到,卡在这里了。


跟踪发现:

github.com/qor5/x/i18n/dyna.go中64行处出错,修改为

if str, ok := val.(string); ok {
	if val.(string) == "" {
		if builder != nil {
			builder.putMissingVal(module, fieldKey, key)
		}
	}
	return strings.NewReplacer(args...).Replace(val.(string))
}
return key

即先进行字符串类型判断,若不为字符串,直接就返回key。

若不想通过此方法解决,毕竟有侵入性。官方似乎提供的方法应该是在admin/messages.go中添加英中翻译:
type Messages_ModelsI18nModuleKey struct { 中添加类似 LearningSystem string
在其后 var Messages_zh_CN_ModelsI18nModuleKey = &Messages_ModelsI18nModuleKey{ 中添加类似 LearningSystem: “学习系统”,
然后才能在admin/config.go中 b.BrandTitle(“LearningSystem”)


最简

结果只是一个显示当前时间的标签,一个DoActive1按钮

package main

import (
	"log"
	"net/http"
	"os"
	"time"

	"github.com/qor5/web"
	. "github.com/theplant/htmlgo"
)

const doAction1 = "doAction1"

func Home(ctx *web.EventContext) (r web.PageResponse, err error) {
	r.Body = Div(
		H1(time.Now().String()),
		Button("DoAction1").Attr("@click",
			web.Plaid().EventFunc(doAction1).Query("id", "1").Go(),
		))
	return
}

func DoAction1(ctx *web.EventContext) (r web.EventResponse, err error) {
	r.Reload = true
	return
}

func layout(in web.PageFunc) (out web.PageFunc) {
	return func(ctx *web.EventContext) (pr web.PageResponse, err error) {

		ctx.Injector.TailHTML(`
			<script src='/main.js'></script>
		`)

		ctx.Injector.HeadHTML(`
		<style>
			[v-cloak] {
				display: none;
			}
		</style>
		`)

		var innerPr web.PageResponse
		innerPr, err = in(ctx)
		if err != nil {
			panic(err)
		}

		pr.Body = innerPr.Body

		return
	}
}

func main() {
	w := web.New()

	mux := http.NewServeMux()
	mux.Handle("/main.js",
		w.PacksHandler("text/javascript",
			web.JSVueComponentsPack(),
			web.JSComponentsPack()))

	mux.Handle("/", w.Page(layout(Home)).
		EventFuncs(doAction1, DoAction1))

	port := os.Getenv("PORT")
	if port == "" {
		port = "9010"
	}
	log.Printf("Listen on %s", port)
	log.Fatal(http.ListenAndServe(":"+port, mux))
}

登陆

此示例地址:https://github.com/qor5/admin/blob/main/login/example/main.go
还支持google和github登陆,但后台不可能允许它。

package main

import (
	"fmt"
	"log"
	"net/http"

	plogin "github.com/qor5/admin/login"
	"github.com/qor5/admin/presets"
	"github.com/qor5/web"
	"github.com/qor5/x/login"
	h "github.com/theplant/htmlgo"
	"github.com/theplant/testingutils"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type User struct {
	gorm.Model

	login.UserPass
	login.OAuthInfo
	login.SessionSecure
}

var (
	db *gorm.DB
)

func init() {
	var err error
	db, err = gorm.Open(sqlite.Open("./login_example.db"), &gorm.Config{
		DisableForeignKeyConstraintWhenMigrating: true,  // 禁止gorm自动建立外键约束
	})
	if err != nil {
		panic(err)
	}
	if err := db.AutoMigrate(&User{}); err != nil { // 生成数据表
		panic(err)
	}

	user := &User{ // 添加一个记录
		UserPass: login.UserPass{
			Account:  "qor@theplant.jp",
			Password: "123",
		},
	}
	user.EncryptPassword()   // 对密码加密
	db.Create(user)
}

func main() {
	pb := presets.New()

	lb := plogin.New(pb).
		DB(db).
		UserModel(&User{}).
		Secret("123").
                // 点击忘记密码,输入邮箱,返回找回密码的链接
		AfterConfirmSendResetPasswordLink(func(r *http.Request, user interface{}, extraVals ...interface{}) error {
			resetLink := extraVals[0]
			fmt.Println("#########################################start")
			testingutils.PrintlnJson(resetLink)
			fmt.Println("#########################################end")
			return nil
		}).
		TOTP(false)

	pb.ProfileFunc(func(ctx *web.EventContext) h.HTMLComponent {
		return h.A(h.Text("logout")).Href(lb.LogoutURL)
	})

	r := http.NewServeMux()
	r.Handle("/", pb)
	lb.Mount(r)

	mux := http.NewServeMux()
	mux.Handle("/", lb.Middleware()(r))
	log.Println("serving at http://localhost:9500")
	log.Fatal(http.ListenAndServe(":9500", mux))
}
	"github.com/markbates/goth/providers/github"
	"github.com/markbates/goth/providers/google"
        "github.com/markbates/goth/providers/twitter"

		OAuthProviders(
			&login.Provider{
				Goth: google.New(os.Getenv("LOGIN_GOOGLE_KEY"), os.Getenv("LOGIN_GOOGLE_SECRET"), "http://localhost:9500/auth/callback?provider=google"),
				Key:  "google",
				Text: "Login with Google",
				Logo: RawHTML(`<svg xmlns="http://www.w3.org/2000/svg" class="inline w-4 h-4 mr-3 text-gray-900 fill-current" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#fbc02d" d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12	s5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24s8.955,20,20,20	s20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"></path><path fill="#e53935" d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039	l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"></path><path fill="#4caf50" d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36	c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"></path><path fill="#1565c0" d="M43.611,20.083L43.595,20L42,20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571	c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"></path></svg>`),
			},
			&login.Provider{
				Goth: twitter.New(os.Getenv("LOGIN_TWITTER_KEY"), os.Getenv("LOGIN_TWITTER_SECRET"), "http://localhost:9500/auth/callback?provider=twitter"),
				Key:  "twitter",
				Text: "Login with Twitter",
				Logo: RawHTML(`<svg class="inline w-4 h-4 mr-3 text-gray-900 fill-current" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"	 viewBox="0 0 248 204" style="enable-background:new 0 0 248 204;" xml:space="preserve"><style type="text/css">	.st0{fill:#1D9BF0;}</style><g id="Logo_1_">	<path id="white_background" class="st0" d="M221.95,51.29c0.15,2.17,0.15,4.34,0.15,6.53c0,66.73-50.8,143.69-143.69,143.69v-0.04		C50.97,201.51,24.1,193.65,1,178.83c3.99,0.48,8,0.72,12.02,0.73c22.74,0.02,44.83-7.61,62.72-21.66		c-21.61-0.41-40.56-14.5-47.18-35.07c7.57,1.46,15.37,1.16,22.8-0.87C27.8,117.2,10.85,96.5,10.85,72.46c0-0.22,0-0.43,0-0.64		c7.02,3.91,14.88,6.08,22.92,6.32C11.58,63.31,4.74,33.79,18.14,10.71c25.64,31.55,63.47,50.73,104.08,52.76		c-4.07-17.54,1.49-35.92,14.61-48.25c20.34-19.12,52.33-18.14,71.45,2.19c11.31-2.23,22.15-6.38,32.07-12.26		c-3.77,11.69-11.66,21.62-22.2,27.93c10.01-1.18,19.79-3.86,29-7.95C240.37,35.29,231.83,44.14,221.95,51.29z"/></g></svg>`),
			},
			&login.Provider{
				Goth: github.New(os.Getenv("LOGIN_GITHUB_KEY"), os.Getenv("LOGIN_GITHUB_SECRET"), "http://localhost/auth/callback?provider=github"),
				Key:  "github",
				Text: "Login with Github",
				Logo: RawHTML(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="16px" height="16px"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>`),
			})
                        ).

相关文章