之前学习过QOR,熟悉以后可以快速的构建后台。今天看它悄悄更新了,名为QOR5,学起来。
官网如前
文档亦如前,不过看起来示例多了。
Github发现的人不多。
1分钟感受
官方文档有一篇1分钟快速入门,硬是多研究了一下才成功运行。
安装QOR5示例构建工具
go install github.com/qor5/docs/cmd/qor5@latest
运行QOR5构建
qor5
提示输入包名等,按提示选择或输入
进入目录运行
- 用 docker-compose up 启动一个postgresql数据库docker
- 运行 source dev_env ,通过环境变量设置相关
- 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>`),
})
).