chore: wip

This commit is contained in:
tangtanglove
2023-10-28 16:00:47 +08:00
parent 05fdf4c3e6
commit e0744c6ebf
9 changed files with 458 additions and 16 deletions

View File

@@ -1,9 +1,11 @@
package main package main
import ( import (
"github.com/quarkcms/quark-go/v2/pkg/app/admin/install" admininstall "github.com/quarkcms/quark-go/v2/pkg/app/admin/install"
"github.com/quarkcms/quark-go/v2/pkg/app/admin/middleware" adminmiddleware "github.com/quarkcms/quark-go/v2/pkg/app/admin/middleware"
adminservice "github.com/quarkcms/quark-go/v2/pkg/app/admin/service" adminservice "github.com/quarkcms/quark-go/v2/pkg/app/admin/service"
miniappinstall "github.com/quarkcms/quark-go/v2/pkg/app/miniapp/install"
miniappmiddleware "github.com/quarkcms/quark-go/v2/pkg/app/miniapp/middleware"
miniappservice "github.com/quarkcms/quark-go/v2/pkg/app/miniapp/service" miniappservice "github.com/quarkcms/quark-go/v2/pkg/app/miniapp/service"
toolservice "github.com/quarkcms/quark-go/v2/pkg/app/tool/service" toolservice "github.com/quarkcms/quark-go/v2/pkg/app/tool/service"
"github.com/quarkcms/quark-go/v2/pkg/builder" "github.com/quarkcms/quark-go/v2/pkg/builder"
@@ -44,11 +46,17 @@ func main() {
// WEB根目录 // WEB根目录
b.Static("/", "./web/app") b.Static("/", "./web/app")
// 自动构建数据库、拉取静态文件 // 构建管理后台数据库
install.Handle() admininstall.Handle()
// 后台中间件 // 管理后台中间件
b.Use(middleware.Handle) b.Use(adminmiddleware.Handle)
// 构建MiniApp数据库
miniappinstall.Handle()
// MiniApp中间件
b.Use(miniappmiddleware.Handle)
// 响应Get请求 // 响应Get请求
b.GET("/", func(ctx *builder.Context) error { b.GET("/", func(ctx *builder.Context) error {

View File

@@ -4,7 +4,6 @@ import (
"github.com/quarkcms/quark-go/v2/pkg/app/admin/install" "github.com/quarkcms/quark-go/v2/pkg/app/admin/install"
"github.com/quarkcms/quark-go/v2/pkg/app/admin/middleware" "github.com/quarkcms/quark-go/v2/pkg/app/admin/middleware"
adminservice "github.com/quarkcms/quark-go/v2/pkg/app/admin/service" adminservice "github.com/quarkcms/quark-go/v2/pkg/app/admin/service"
mixservice "github.com/quarkcms/quark-go/v2/pkg/app/mix/service"
toolservice "github.com/quarkcms/quark-go/v2/pkg/app/tool/service" toolservice "github.com/quarkcms/quark-go/v2/pkg/app/tool/service"
"github.com/quarkcms/quark-go/v2/pkg/builder" "github.com/quarkcms/quark-go/v2/pkg/builder"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
@@ -22,9 +21,6 @@ func main() {
// 加载后台服务 // 加载后台服务
providers = append(providers, adminservice.Providers...) providers = append(providers, adminservice.Providers...)
// 加载Mix服务
providers = append(providers, mixservice.Providers...)
// 加载工具服务 // 加载工具服务
providers = append(providers, toolservice.Providers...) providers = append(providers, toolservice.Providers...)

View File

@@ -13,6 +13,7 @@ var Providers = []interface{}{
&logins.Index{}, &logins.Index{},
&layouts.Index{}, &layouts.Index{},
&dashboards.Index{}, &dashboards.Index{},
&resources.User{},
&resources.Admin{}, &resources.Admin{},
&resources.Role{}, &resources.Role{},
&resources.Permission{}, &resources.Permission{},

View File

@@ -0,0 +1,192 @@
package resources
import (
"strconv"
"time"
"github.com/quarkcms/quark-go/v2/pkg/app/admin/component/form/fields/radio"
"github.com/quarkcms/quark-go/v2/pkg/app/admin/component/form/rule"
"github.com/quarkcms/quark-go/v2/pkg/app/admin/service/actions"
"github.com/quarkcms/quark-go/v2/pkg/app/admin/service/searches"
"github.com/quarkcms/quark-go/v2/pkg/app/admin/template/resource"
"github.com/quarkcms/quark-go/v2/pkg/app/miniapp/model"
"github.com/quarkcms/quark-go/v2/pkg/builder"
"github.com/quarkcms/quark-go/v2/pkg/utils/hash"
)
type User struct {
resource.Template
}
// 初始化
func (p *User) Init(ctx *builder.Context) interface{} {
// 标题
p.Title = "用户"
// 模型
p.Model = &model.User{}
// 分页
p.PerPage = 10
// 是否具有导出功能
p.WithExport = true
return p
}
// 字段
func (p *User) Fields(ctx *builder.Context) []interface{} {
field := &resource.Field{}
return []interface{}{
field.ID("id", "ID"),
field.Image("avatar", "头像").OnlyOnForms(),
field.Text("username", "用户名", func() interface{} {
return "<a href='#/layout/index?api=/api/admin/user/edit&id=" + strconv.Itoa(p.Field["id"].(int)) + "'>" + p.Field["username"].(string) + "</a>"
}).
SetRules([]*rule.Rule{
rule.Required(true, "用户名必须填写"),
rule.Min(6, "用户名不能少于6个字符"),
rule.Max(20, "用户名不能超过20个字符"),
}).
SetCreationRules([]*rule.Rule{
rule.Unique("users", "username", "用户名已存在"),
}).
SetUpdateRules([]*rule.Rule{
rule.Unique("users", "username", "{id}", "用户名已存在"),
}),
field.Text("nickname", "昵称").
SetEditable(true).
SetRules([]*rule.Rule{
rule.Required(true, "昵称必须填写"),
}),
field.Text("email", "邮箱").
SetRules([]*rule.Rule{
rule.Required(true, "邮箱必须填写"),
rule.Email("邮箱格式错误"),
}).
SetCreationRules([]*rule.Rule{
rule.Unique("users", "email", "邮箱已存在"),
}).
SetUpdateRules([]*rule.Rule{
rule.Unique("users", "email", "{id}", "邮箱已存在"),
}),
field.Text("phone", "手机号").
SetRules([]*rule.Rule{
rule.Required(true, "手机号必须填写"),
rule.Phone("手机号格式错误"),
}).
SetCreationRules([]*rule.Rule{
rule.Unique("users", "phone", "手机号已存在"),
}).
SetUpdateRules([]*rule.Rule{
rule.Unique("users", "phone", "{id}", "手机号已存在"),
}),
field.Radio("sex", "性别").
SetRules([]*rule.Rule{
rule.Required(true, "请选择性别"),
}).
SetOptions([]*radio.Option{
{
Value: 1,
Label: "男",
},
{
Value: 2,
Label: "女",
},
}).
SetFilters(true).
SetDefault(1),
field.Password("password", "密码").
SetCreationRules([]*rule.Rule{
rule.Required(true, "密码必须填写"),
}).
OnlyOnForms().
ShowOnImporting(true),
field.Datetime("last_login_time", "最后登录时间", func() interface{} {
if p.Field["last_login_time"] == nil {
return p.Field["last_login_time"]
}
if p.Field["last_login_time"].(time.Time).Format("2006-01-02 15:04:05") == "0001-01-01 00:00:00" {
return nil
}
return p.Field["last_login_time"].(time.Time).Format("2006-01-02 15:04:05")
}).OnlyOnIndex(),
field.Switch("status", "状态").
SetRules([]*rule.Rule{
rule.Required(true, "请选择状态"),
}).
SetTrueValue("正常").
SetFalseValue("禁用").
SetEditable(true).
SetDefault(true),
}
}
// 搜索
func (p *User) Searches(ctx *builder.Context) []interface{} {
return []interface{}{
searches.Input("username", "用户名"),
searches.Input("nickname", "昵称"),
searches.Status(),
searches.DatetimeRange("last_login_time", "登录时间"),
}
}
// 行为
func (p *User) Actions(ctx *builder.Context) []interface{} {
return []interface{}{
actions.Import(),
actions.CreateLink(),
actions.BatchDelete(),
actions.BatchDisable(),
actions.BatchEnable(),
actions.DetailLink(),
actions.More().
SetActions([]interface{}{
actions.EditLink(),
actions.Delete(),
}),
actions.FormSubmit(),
actions.FormReset(),
actions.FormBack(),
actions.FormExtraBack(),
}
}
// 编辑页面显示前回调
func (p *User) BeforeEditing(ctx *builder.Context, data map[string]interface{}) map[string]interface{} {
// 编辑页面清理password
delete(data, "password")
return data
}
// 保存数据前回调
func (p *User) BeforeSaving(ctx *builder.Context, submitData map[string]interface{}) (map[string]interface{}, error) {
// 加密密码
if submitData["password"] != nil {
submitData["password"] = hash.Make(submitData["password"].(string))
}
return submitData, nil
}

View File

@@ -0,0 +1,26 @@
package install
import (
"github.com/quarkcms/quark-go/v2/pkg/app/miniapp/model"
"github.com/quarkcms/quark-go/v2/pkg/dal/db"
"gorm.io/gorm"
)
// 执行安装操作
func Handle() {
// 迁移数据
db.Client.AutoMigrate(
&model.User{},
)
// 如果用户不存在,初始化数据库数据
userInfo, err := (&model.User{}).GetInfoById(1)
if err != nil && err != gorm.ErrRecordNotFound {
panic(err)
}
if userInfo.Id == 0 {
// 数据填充
(&model.User{}).Seeder()
}
}

View File

@@ -0,0 +1,30 @@
package middleware
import (
"strings"
"github.com/quarkcms/quark-go/v2/pkg/app/miniapp/model"
"github.com/quarkcms/quark-go/v2/pkg/builder"
)
// 中间件
func Handle(ctx *builder.Context) error {
// 排除非后台路由
if !strings.Contains(ctx.Path(), "api/miniapp/user") {
return ctx.Next()
}
// 获取登录信息
userInfo, err := (&model.User{}).GetAuthUser(ctx.Engine.GetConfig().AppKey, ctx.Token())
if err != nil {
return ctx.JSON(401, builder.Error(err.Error()))
}
guardName := userInfo.GuardName
if guardName != "user" {
return ctx.JSON(401, builder.Error("401 Unauthozied"))
}
return ctx.Next()
}

View File

@@ -0,0 +1,145 @@
package model
import (
"errors"
"time"
"github.com/golang-jwt/jwt/v4"
adminmodel "github.com/quarkcms/quark-go/v2/pkg/app/admin/model"
"github.com/quarkcms/quark-go/v2/pkg/dal/db"
"github.com/quarkcms/quark-go/v2/pkg/utils/hash"
"gorm.io/gorm"
)
// 字段
type User struct {
Id int `json:"id" gorm:"autoIncrement"`
Username string `json:"username" gorm:"size:20;index:Users_username_unique,unique;not null"`
Nickname string `json:"nickname" gorm:"size:200;not null"`
Sex int `json:"sex" gorm:"size:4;not null;default:1"`
Email string `json:"email" gorm:"size:50;index:users_email_unique,unique;not null"`
Phone string `json:"phone" gorm:"size:11;index:users_phone_unique,unique;not null"`
Password string `json:"password" gorm:"size:255;not null"`
Avatar string `json:"avatar" gorm:"size:1000"`
LastLoginIp string `json:"last_login_ip" gorm:"size:255"`
LastLoginTime time.Time `json:"last_login_time"`
WxOpenid string `json:"wx_openid" gorm:"size:255"`
WxUnionid string `json:"wx_unionid" gorm:"size:255"`
Status int `json:"status" gorm:"size:1;not null;default:1"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"deleted_at"`
}
// 用户JWT结构体
type UserClaims struct {
Id int `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Sex int `json:"sex"`
Email string `json:"email"`
Phone string `json:"phone"`
Avatar string `json:"avatar"`
GuardName string `json:"guard_name"`
jwt.RegisteredClaims
}
// 用户Seeder
func (model *User) Seeder() {
// 如果菜单已存在不执行Seeder操作
if (&adminmodel.Menu{}).IsExist(18) {
return
}
// 创建菜单
menuSeeders := []*adminmodel.Menu{
{Id: 18, Name: "用户管理", GuardName: "admin", Icon: "icon-user", Type: 1, Pid: 0, Sort: 0, Path: "/user", Show: 1, IsEngine: 0, IsLink: 0, Status: 1},
{Id: 19, Name: "用户列表", GuardName: "admin", Icon: "", Type: 2, Pid: 18, Sort: 0, Path: "/api/admin/user/index", Show: 1, IsEngine: 1, IsLink: 0, Status: 1},
}
db.Client.Create(&menuSeeders)
seeders := []User{
{Username: "tangtanglove", Nickname: "默认用户", Email: "tangtanglove@yourweb.com", Phone: "10086", Password: hash.Make("123456"), Sex: 1, Status: 1, LastLoginTime: time.Now()},
}
db.Client.Create(&seeders)
}
// 获取用户JWT信息
func (model *User) GetClaims(UserInfo *User) (userClaims *UserClaims) {
userClaims = &UserClaims{
UserInfo.Id,
UserInfo.Username,
UserInfo.Nickname,
UserInfo.Sex,
UserInfo.Email,
UserInfo.Phone,
UserInfo.Avatar,
"user",
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 过期时间默认24小时
IssuedAt: jwt.NewNumericDate(time.Now()), // 颁发时间
NotBefore: jwt.NewNumericDate(time.Now()), // 不早于时间
Issuer: "QuarkGo", // 颁发人
Subject: "User Token", // 主题信息
},
}
return userClaims
}
// 获取当前认证的用户信息默认参数为tokenString
func (model *User) GetAuthUser(appKey string, tokenString string) (userClaims *UserClaims, Error error) {
token, err := jwt.ParseWithClaims(tokenString, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(appKey), nil
})
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, errors.New("token格式错误")
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return nil, errors.New("token已过期")
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, errors.New("token未生效")
} else {
return nil, err
}
}
}
if claims, ok := token.Claims.(*UserClaims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("token不可用")
}
// 通过ID获取用户信息
func (model *User) GetInfoById(id interface{}) (User *User, Error error) {
err := db.Client.Where("status = ?", 1).Where("id = ?", id).First(&User).Error
return User, err
}
// 通过用户名获取用户信息
func (model *User) GetInfoByUsername(username string) (User *User, Error error) {
err := db.Client.Where("status = ?", 1).Where("username = ?", username).First(&User).Error
if User.Avatar != "" {
User.Avatar = (&adminmodel.Picture{}).GetPath(User.Avatar) // 获取头像地址
}
return User, err
}
// 更新最后一次登录数据
func (model *User) UpdateLastLogin(uid int, lastLoginIp string, lastLoginTime time.Time) error {
data := User{
LastLoginIp: lastLoginIp,
LastLoginTime: lastLoginTime,
}
return db.Client.
Where("id = ?", uid).
Updates(&data).Error
}

View File

@@ -1,7 +1,6 @@
package pages package pages
import ( import (
"github.com/quarkcms/quark-go/v2/pkg/app/miniapp/component/navbar"
"github.com/quarkcms/quark-go/v2/pkg/app/miniapp/template/page" "github.com/quarkcms/quark-go/v2/pkg/app/miniapp/template/page"
"github.com/quarkcms/quark-go/v2/pkg/builder" "github.com/quarkcms/quark-go/v2/pkg/builder"
) )
@@ -15,11 +14,6 @@ func (p *My) Init(ctx *builder.Context) interface{} {
return p return p
} }
// 头部导航
func (p *My) Navbar(ctx *builder.Context, navbar *navbar.Component) interface{} {
return navbar.SetTitle("我的")
}
// 组件渲染 // 组件渲染
func (p *My) Content(ctx *builder.Context) interface{} { func (p *My) Content(ctx *builder.Context) interface{} {
return "我的" return "我的"

View File

@@ -0,0 +1,50 @@
package login
import (
"github.com/quarkcms/quark-go/v2/pkg/app/miniapp/template/page"
"github.com/quarkcms/quark-go/v2/pkg/builder"
"github.com/quarkcms/quark-go/v2/pkg/dal/db"
)
// 后台登录模板
type Template struct {
page.Template
FromStyle string
Api string
}
// 初始化
func (p *Template) Init(ctx *builder.Context) interface{} {
return p
}
// 初始化模板
func (p *Template) TemplateInit(ctx *builder.Context) interface{} {
// 初始化数据对象
p.DB = db.Client
// 标题
p.Title = "登录"
return p
}
// 初始化路由映射
func (p *Template) RouteInit() interface{} {
p.GET("/api/miniapp/login/:resource/index", p.Render) // 渲染登录页面路由
p.POST("/api/miniapp/login/:resource/handle", p.Handle) // 后台登录执行路由
return p
}
// 内容
func (p *Template) Content(ctx *builder.Context) interface{} {
return "登录页面"
}
// 执行表单
func (p *Template) Handle(ctx *builder.Context) error {
return ctx.JSONError("请自行处理表单逻辑")
}