mirror of https://github.com/veypi/OneAuth.git
remove old
parent
809bba7417
commit
45708ca4f0
@ -1,45 +0,0 @@
|
||||
//
|
||||
// create.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
)
|
||||
|
||||
type createOpts struct {
|
||||
AppID string `json:"app_id" src:"json" desc:"应用ID"`
|
||||
UserID *string `json:"user_id" src:"json" desc:"用户ID"`
|
||||
RoleID *string `json:"role_id" src:"json" desc:"角色ID"`
|
||||
ResourceID *string `json:"resource_id" src:"json" desc:"资源ID"`
|
||||
Name string `json:"name" src:"json" desc:"名称"`
|
||||
TID string `json:"tid" src:"json" desc:"资源ID"`
|
||||
Level uint `json:"level" src:"json" desc:"级别"`
|
||||
}
|
||||
|
||||
var _ = Router.Post("/", "创建访问权限", createAccess)
|
||||
|
||||
func createAccess(x *vigo.X, opts *createOpts) (*models.Access, error) {
|
||||
// 创建新记录
|
||||
access := models.Access{
|
||||
AppID: opts.AppID,
|
||||
UserID: opts.UserID,
|
||||
RoleID: opts.RoleID,
|
||||
ResourceID: opts.ResourceID,
|
||||
Name: opts.Name,
|
||||
TID: opts.TID,
|
||||
Level: opts.Level,
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
if err := cfg.DB().Create(&access).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &access, nil
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
//
|
||||
// del.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type deleteOpts struct {
|
||||
ID string `src:"path@id" desc:"记录ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{id}", "删除访问权限", deleteAccess)
|
||||
|
||||
type deleteResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func deleteAccess(x *vigo.X, opts *deleteOpts) (*deleteResponse, error) {
|
||||
// 查找记录
|
||||
var access models.Access
|
||||
if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil {
|
||||
return nil, vigo.NewError("未找到资源").WithCode(404)
|
||||
}
|
||||
|
||||
// 删除记录
|
||||
if err := cfg.DB().Delete(&access).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &deleteResponse{Message: "删除成功"}, nil
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
//
|
||||
// get.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type getIDReq struct {
|
||||
ID string `src:"path@id" desc:"记录ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{id}", "获取访问权限详情", getAccess)
|
||||
|
||||
func getAccess(x *vigo.X, req *getIDReq) (*models.Access, error) {
|
||||
// 查询数据库
|
||||
var access models.Access
|
||||
err := cfg.DB().Where("id = ?", req.ID).First(&access).Error
|
||||
if err != nil {
|
||||
return nil, vigo.NewError("未找到资源").WithCode(404)
|
||||
}
|
||||
|
||||
return &access, nil
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 16:28:13
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
// Auto generated by OneBD. DO NOT EDIT
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
@ -1,64 +0,0 @@
|
||||
//
|
||||
// list.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type listOpts struct {
|
||||
Page int `src:"query" desc:"页码" default:"1"`
|
||||
PageSize int `src:"query" desc:"每页大小" default:"20"`
|
||||
AppID string `src:"query" desc:"应用ID"`
|
||||
UserID string `src:"query" desc:"用户ID"`
|
||||
RoleID string `src:"query" desc:"角色ID"`
|
||||
Name string `src:"query" desc:"名称"`
|
||||
}
|
||||
|
||||
type listResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []models.Access `json:"items"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/", "获取访问权限列表", listAccess)
|
||||
|
||||
func listAccess(x *vigo.X, opts *listOpts) (*listResponse, error) {
|
||||
// 构建查询
|
||||
db := cfg.DB().Model(&models.Access{})
|
||||
if opts.AppID != "" {
|
||||
db = db.Where("app_id = ?", opts.AppID)
|
||||
}
|
||||
if opts.UserID != "" {
|
||||
db = db.Where("user_id = ?", opts.UserID)
|
||||
}
|
||||
if opts.RoleID != "" {
|
||||
db = db.Where("role_id = ?", opts.RoleID)
|
||||
}
|
||||
if opts.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+opts.Name+"%")
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
var items []models.Access
|
||||
if err := db.Offset(offset).Limit(opts.PageSize).Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listResponse{
|
||||
Total: total,
|
||||
Items: items,
|
||||
}, nil
|
||||
}
|
||||
@ -1,56 +0,0 @@
|
||||
//
|
||||
// patch.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type updateOpts struct {
|
||||
ID string `src:"path@id" desc:"记录ID"`
|
||||
Name *string `json:"name" src:"json" desc:"名称"`
|
||||
TID *string `json:"tid" src:"json" desc:"资源ID"`
|
||||
Level *uint `json:"level" src:"json" desc:"级别"`
|
||||
}
|
||||
|
||||
var _ = Router.Patch("/{id}", "更新访问权限", updateAccess)
|
||||
|
||||
func updateAccess(x *vigo.X, opts *updateOpts) (*models.Access, error) {
|
||||
// 查找记录
|
||||
var access models.Access
|
||||
if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil {
|
||||
return nil, vigo.NewError("未找到资源").WithCode(404)
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
updates := map[string]interface{}{}
|
||||
if opts.Name != nil {
|
||||
updates["name"] = *opts.Name
|
||||
}
|
||||
if opts.TID != nil {
|
||||
updates["tid"] = *opts.TID
|
||||
}
|
||||
if opts.Level != nil {
|
||||
updates["level"] = *opts.Level
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
if len(updates) > 0 {
|
||||
if err := cfg.DB().Model(&access).Updates(updates).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 返回更新后的记录
|
||||
if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &access, nil
|
||||
}
|
||||
@ -1,167 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/libs/auth"
|
||||
"github.com/veypi/vbase/libs/utils"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type appIDReq struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{app_id}/key", "重置应用密钥", auth.Check("app", "app_id", auth.DoDelete), appKey)
|
||||
|
||||
func appKey(x *vigo.X, req *appIDReq) (string, error) {
|
||||
data := &models.App{}
|
||||
data.ID = req.AppID
|
||||
key := utils.RandSeq(32)
|
||||
err := cfg.DB().Model(data).Update("key", key).Error
|
||||
|
||||
return key, err
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{app_id}", "删除应用", auth.Check("app", "app_id", auth.DoDelete), appDelete)
|
||||
|
||||
func appDelete(x *vigo.X, req *appIDReq) (*models.App, error) {
|
||||
data := &models.App{}
|
||||
|
||||
err := cfg.DB().Where("id = ?", req.AppID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{app_id}", "获取应用详情", auth.Check("app", "app_id", auth.DoRead), appGet)
|
||||
|
||||
func appGet(x *vigo.X, req *appIDReq) (*models.App, error) {
|
||||
data := &models.App{}
|
||||
err := cfg.DB().Where("id = ?", req.AppID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
type listOpts struct {
|
||||
Name *string `json:"name" src:"query" desc:"应用名称"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/", "获取应用列表", appList)
|
||||
|
||||
type appListItem struct {
|
||||
models.App
|
||||
UserStatus string `json:"user_status"`
|
||||
}
|
||||
|
||||
func appList(x *vigo.X, opts *listOpts) ([]*appListItem, error) {
|
||||
data := make([]*appListItem, 0, 10)
|
||||
token, err := auth.CheckJWT(x)
|
||||
if err == nil {
|
||||
uid := token.UID
|
||||
query := cfg.DB().Table("apps").Select("apps.*,app_users.status user_status")
|
||||
if opts.Name != nil {
|
||||
query = query.Joins("LEFT JOIN app_users ON app_users.app_id = apps.id AND app_users.user_id = ? AND apps.name LIKE ?", uid, opts.Name)
|
||||
} else {
|
||||
query = query.Joins("LEFT JOIN app_users ON app_users.app_id = apps.id AND app_users.user_id = ?", uid)
|
||||
}
|
||||
err = query.Scan(&data).Error
|
||||
} else {
|
||||
err = cfg.DB().Table("apps").Select("id", "name", "icon").Find(&data).Error
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
type postOpts struct {
|
||||
Name string `json:"name" src:"json" desc:"应用名称"`
|
||||
Icon string `json:"icon" src:"json" desc:"图标"`
|
||||
Des *string `json:"des" src:"json" desc:"描述"`
|
||||
Typ string `json:"typ" src:"json" desc:"类型"`
|
||||
Status string `json:"status" src:"json" desc:"状态"`
|
||||
InitUrl string `json:"init_url" src:"json" desc:"初始化URL"`
|
||||
}
|
||||
|
||||
var _ = Router.Post("/", "创建应用", auth.Check("app", "", auth.DoCreate), appPost)
|
||||
|
||||
func appPost(x *vigo.X, opts *postOpts) (*models.App, error) {
|
||||
data := &models.App{}
|
||||
data.Name = opts.Name
|
||||
data.Icon = opts.Icon
|
||||
if data.Icon == "" {
|
||||
data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))
|
||||
}
|
||||
data.Des = ""
|
||||
if opts.Des != nil {
|
||||
data.Des = *opts.Des
|
||||
}
|
||||
data.Typ = opts.Typ
|
||||
data.Status = opts.Status
|
||||
data.InitUrl = opts.InitUrl
|
||||
err := cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
data.Key = utils.RandSeq(32)
|
||||
err := tx.Create(data).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid, _ := x.Get("uid").(string)
|
||||
au := &models.AppUser{
|
||||
AppID: data.ID,
|
||||
UserID: uid,
|
||||
Status: models.AUSTATUS_OK,
|
||||
}
|
||||
return tx.Create(au).Error
|
||||
})
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
type patchOpts struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)" src:"path@app_id" desc:"应用ID"`
|
||||
Name *string `json:"name" src:"json" desc:"应用名称"`
|
||||
Icon *string `json:"icon" src:"json" desc:"图标"`
|
||||
Des *string `json:"des" src:"json" desc:"描述"`
|
||||
Typ *string `json:"typ" gorm:"default:auto" src:"json" desc:"类型"`
|
||||
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36)" src:"json" desc:"初始角色ID"`
|
||||
Status *string `json:"status" gorm:"default:ok" src:"json" desc:"状态"`
|
||||
InitUrl *string `json:"init_url" src:"json" desc:"初始化URL"`
|
||||
}
|
||||
|
||||
var _ = Router.Patch("/{app_id}", "更新应用", auth.Check("app", "app_id", auth.DoUpdate), appPatch)
|
||||
|
||||
func appPatch(x *vigo.X, opts *patchOpts) (*models.App, error) {
|
||||
data := &models.App{}
|
||||
|
||||
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.Name != nil {
|
||||
optsMap["name"] = opts.Name
|
||||
}
|
||||
if opts.Icon != nil {
|
||||
optsMap["icon"] = opts.Icon
|
||||
}
|
||||
if opts.Des != nil {
|
||||
optsMap["des"] = opts.Des
|
||||
}
|
||||
if opts.Typ != nil {
|
||||
optsMap["typ"] = opts.Typ
|
||||
}
|
||||
if opts.InitRoleID != nil {
|
||||
optsMap["init_role_id"] = opts.InitRoleID
|
||||
}
|
||||
if opts.Status != nil {
|
||||
optsMap["status"] = opts.Status
|
||||
}
|
||||
if opts.InitUrl != nil {
|
||||
optsMap["init_url"] = opts.InitUrl
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package app_user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type createOpts struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"`
|
||||
UserID string `json:"user_id" src:"json" desc:"用户ID"`
|
||||
Status string `json:"status" src:"json" desc:"状态" default:"ok"`
|
||||
}
|
||||
|
||||
var _ = Router.Post("/", "创建应用用户", createAppUser)
|
||||
|
||||
func createAppUser(x *vigo.X, opts *createOpts) (*models.AppUser, error) {
|
||||
appUser := &models.AppUser{
|
||||
AppID: opts.AppID,
|
||||
UserID: opts.UserID,
|
||||
Status: opts.Status,
|
||||
}
|
||||
|
||||
if err := cfg.DB().Create(appUser).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return appUser, nil
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package app_user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type deleteOpts struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"`
|
||||
UserID string `src:"path@user_id" desc:"用户ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{user_id}", "删除应用用户", deleteAppUser)
|
||||
|
||||
type deleteAppUserResponse struct {
|
||||
Message string `json:"message"`
|
||||
AppID string `json:"app_id"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
func deleteAppUser(x *vigo.X, opts *deleteOpts) (*deleteAppUserResponse, error) {
|
||||
appUser := &models.AppUser{}
|
||||
if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil {
|
||||
return nil, vigo.NewError("app_user not found").WithCode(404)
|
||||
}
|
||||
|
||||
if err := cfg.DB().Delete(appUser).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &deleteAppUserResponse{
|
||||
Message: "app_user deleted successfully",
|
||||
AppID: opts.AppID,
|
||||
UserID: opts.UserID,
|
||||
}, nil
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
package app_user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type appUserIDReq struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"`
|
||||
UserID string `src:"path@user_id" desc:"用户ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{user_id}", "获取应用用户详情", getAppUser)
|
||||
|
||||
func getAppUser(x *vigo.X, req *appUserIDReq) (*models.AppUser, error) {
|
||||
appUser := &models.AppUser{}
|
||||
err := cfg.DB().Where("app_id = ? AND user_id = ?", req.AppID, req.UserID).First(appUser).Error
|
||||
if err != nil {
|
||||
return nil, vigo.NewError("app_user not found").WithCode(404)
|
||||
}
|
||||
|
||||
return appUser, nil
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
//
|
||||
// init.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-13 16:47
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package app_user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
@ -1,47 +0,0 @@
|
||||
package app_user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type listOpts struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"`
|
||||
Page int `src:"query" desc:"页码" default:"1"`
|
||||
PageSize int `src:"query" desc:"每页数量" default:"20"`
|
||||
Status string `src:"query" desc:"状态" default:""`
|
||||
}
|
||||
|
||||
type listResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []*models.AppUser `json:"items"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/", "获取应用用户列表", listAppUsers)
|
||||
|
||||
func listAppUsers(x *vigo.X, opts *listOpts) (*listResponse, error) {
|
||||
query := cfg.DB().Model(&models.AppUser{}).Where("app_id = ?", opts.AppID)
|
||||
|
||||
if opts.Status != "" {
|
||||
query = query.Where("status = ?", opts.Status)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
var users []*models.AppUser
|
||||
if err := query.Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listResponse{
|
||||
Total: total,
|
||||
Items: users,
|
||||
}, nil
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
package app_user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type updateOpts struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"`
|
||||
UserID string `src:"path@user_id" desc:"用户ID"`
|
||||
Status *string `json:"status" src:"json" desc:"状态"`
|
||||
}
|
||||
|
||||
var _ = Router.Patch("/{user_id}", "更新应用用户", updateAppUser)
|
||||
|
||||
func updateAppUser(x *vigo.X, opts *updateOpts) (*models.AppUser, error) {
|
||||
appUser := &models.AppUser{}
|
||||
if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil {
|
||||
return nil, vigo.NewError("app_user not found").WithCode(404)
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{}
|
||||
if opts.Status != nil {
|
||||
updates["status"] = *opts.Status
|
||||
}
|
||||
|
||||
if len(updates) > 0 {
|
||||
if err := cfg.DB().Model(appUser).Updates(updates).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return appUser, nil
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 16:28:13
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
// Auto generated by OneBD. DO NOT EDIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/api/app/access"
|
||||
"github.com/veypi/vbase/api/app/app_user"
|
||||
"github.com/veypi/vbase/api/app/resource"
|
||||
"github.com/veypi/vbase/api/app/role"
|
||||
"github.com/veypi/vbase/libs/auth"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
var appRouter = Router.SubRouter("/{app_id}").Use(auth.Check("app", "{app_id}", 2))
|
||||
|
||||
func init() {
|
||||
appRouter.Extend("/resource", resource.Router)
|
||||
appRouter.Extend("/user", app_user.Router)
|
||||
appRouter.Extend("/role", role.Router)
|
||||
appRouter.Extend("/access", access.Router)
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type createOpts struct {
|
||||
AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"`
|
||||
Name string `json:"name" src:"json" desc:"资源名称"`
|
||||
Des string `json:"des" src:"json" desc:"资源描述"`
|
||||
}
|
||||
|
||||
var _ = Router.Post("/", "创建资源", createResource)
|
||||
|
||||
func createResource(x *vigo.X, opts *createOpts) (*models.Resource, error) {
|
||||
// 创建资源对象
|
||||
resource := &models.Resource{
|
||||
AppID: opts.AppID,
|
||||
Name: opts.Name,
|
||||
Des: opts.Des,
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
if err := cfg.DB().Create(resource).Error; err != nil {
|
||||
return nil, vigo.NewError("failed to create resource").WithCode(500)
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type deleteOpts struct {
|
||||
ResourceID string `src:"path@resource_id" desc:"资源ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{resource_id}", "删除资源", deleteResource)
|
||||
|
||||
type deleteResourceResponse struct {
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func deleteResource(x *vigo.X, opts *deleteOpts) (*deleteResourceResponse, error) {
|
||||
// 查找资源
|
||||
resource := &models.Resource{}
|
||||
if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil {
|
||||
return nil, vigo.NewError("resource not found").WithCode(404)
|
||||
}
|
||||
|
||||
// 删除资源
|
||||
if err := cfg.DB().Delete(resource).Error; err != nil {
|
||||
return nil, vigo.NewError("failed to delete resource").WithCode(500)
|
||||
}
|
||||
|
||||
return &deleteResourceResponse{
|
||||
Message: "resource deleted successfully",
|
||||
ID: opts.ResourceID,
|
||||
}, nil
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type getIDReq struct {
|
||||
ID string `src:"path@resource_id" desc:"资源ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{resource_id}", "获取资源详情", getResource)
|
||||
|
||||
func getResource(x *vigo.X, req *getIDReq) (*models.Resource, error) {
|
||||
// 查询数据库
|
||||
resource := &models.Resource{}
|
||||
err := cfg.DB().Where("id = ?", req.ID).First(resource).Error
|
||||
if err != nil {
|
||||
return nil, vigo.NewError("resource not found").WithCode(404)
|
||||
}
|
||||
|
||||
return resource, nil
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
//
|
||||
// init.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-13 16:45
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
@ -1,50 +0,0 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type listOpts struct {
|
||||
Page int `src:"query" desc:"页码" default:"1"`
|
||||
PageSize int `src:"query" desc:"每页数量" default:"20"`
|
||||
AppID string `src:"query" desc:"应用ID"`
|
||||
Name string `src:"query" desc:"资源名称"`
|
||||
}
|
||||
|
||||
type listResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []*models.Resource `json:"items"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/", "获取资源列表", listResources)
|
||||
|
||||
func listResources(x *vigo.X, opts *listOpts) (*listResponse, error) {
|
||||
// 构建查询
|
||||
db := cfg.DB().Model(&models.Resource{})
|
||||
if opts.AppID != "" {
|
||||
db = db.Where("app_id = ?", opts.AppID)
|
||||
}
|
||||
if opts.Name != "" {
|
||||
db = db.Where("name LIKE ?", "%"+opts.Name+"%")
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
var resources []*models.Resource
|
||||
if err := db.Offset(offset).Limit(opts.PageSize).Find(&resources).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listResponse{
|
||||
Total: total,
|
||||
Items: resources,
|
||||
}, nil
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type updateOpts struct {
|
||||
ResourceID string `src:"path@resource_id" desc:"资源ID"`
|
||||
Name *string `json:"name" src:"json" desc:"资源名称"`
|
||||
Des *string `json:"des" src:"json" desc:"资源描述"`
|
||||
}
|
||||
|
||||
var _ = Router.Patch("/{resource_id}", "更新资源", updateResource)
|
||||
|
||||
func updateResource(x *vigo.X, opts *updateOpts) (*models.Resource, error) {
|
||||
// 查找资源
|
||||
resource := &models.Resource{}
|
||||
if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil {
|
||||
return nil, vigo.NewError("resource not found").WithCode(404)
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
updates := map[string]interface{}{}
|
||||
if opts.Name != nil {
|
||||
updates["name"] = *opts.Name
|
||||
}
|
||||
if opts.Des != nil {
|
||||
updates["des"] = *opts.Des
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
if len(updates) > 0 {
|
||||
if err := cfg.DB().Model(resource).Updates(updates).Error; err != nil {
|
||||
return nil, vigo.NewError("failed to update resource").WithCode(500)
|
||||
}
|
||||
}
|
||||
return resource, nil
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type createOpts struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"` // 应用 ID
|
||||
Name string `json:"name" src:"json" desc:"角色名称"` // 角色名称
|
||||
Des string `json:"des" src:"json" desc:"角色描述"` // 角色描述
|
||||
}
|
||||
|
||||
var _ = Router.Post("/", "创建角色", createRole)
|
||||
|
||||
func createRole(x *vigo.X, opts *createOpts) (*models.Role, error) {
|
||||
// 创建角色
|
||||
role := &models.Role{
|
||||
AppID: opts.AppID,
|
||||
Name: opts.Name,
|
||||
Des: opts.Des,
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
if err := cfg.DB().Create(role).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type deleteOpts struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"` // 角色 ID
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{role_id}", "删除角色", deleteRole)
|
||||
|
||||
type deleteRoleResponse struct {
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
func deleteRole(x *vigo.X, opts *deleteOpts) (*deleteRoleResponse, error) {
|
||||
// 查找角色
|
||||
role := &models.Role{}
|
||||
if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil {
|
||||
return nil, vigo.NewError("role not found").WithCode(404)
|
||||
}
|
||||
|
||||
// 执行删除操作
|
||||
if err := cfg.DB().Delete(role).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 返回成功消息
|
||||
return &deleteRoleResponse{
|
||||
Message: "role deleted successfully",
|
||||
ID: opts.RoleID,
|
||||
}, nil
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type getIDReq struct {
|
||||
ID string `src:"path@role_id" desc:"角色ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{role_id}", "获取角色详情", getRole)
|
||||
|
||||
func getRole(x *vigo.X, req *getIDReq) (*models.Role, error) {
|
||||
// 查询数据库
|
||||
role := &models.Role{}
|
||||
err := cfg.DB().Where("id = ?", req.ID).First(role).Error
|
||||
if err != nil {
|
||||
return nil, vigo.NewError("role not found").WithCode(404)
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
//
|
||||
// init.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-13 16:49
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
@ -1,57 +0,0 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var _ = Router.Get("/", "获取角色列表", listRoles)
|
||||
|
||||
type listOpts struct {
|
||||
AppID string `src:"path@app_id" desc:"应用ID"`
|
||||
Page int `src:"query" default:"1" desc:"页码"`
|
||||
PageSize int `src:"query" default:"20" desc:"页大小"`
|
||||
Keyword string `src:"query" default:"" desc:"关键词"`
|
||||
SortBy string `src:"query" default:"created_at" desc:"排序字段"`
|
||||
Order string `src:"query" default:"desc" desc:"排序方向"`
|
||||
}
|
||||
|
||||
type listResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Items []*models.Role `json:"items"`
|
||||
}
|
||||
|
||||
func listRoles(x *vigo.X, opts *listOpts) (*listResponse, error) {
|
||||
// 构建查询
|
||||
db := cfg.DB().Model(&models.Role{}).Where("app_id = ?", opts.AppID)
|
||||
query := db
|
||||
|
||||
// 应用关键词搜索
|
||||
if opts.Keyword != "" {
|
||||
query = query.Where("name LIKE ? OR des LIKE ?", "%"+opts.Keyword+"%", "%"+opts.Keyword+"%")
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 分页和排序
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Order(opts.SortBy + " " + opts.Order)
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 查询数据
|
||||
var roles []*models.Role
|
||||
if err := query.Find(&roles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return &listResponse{
|
||||
Total: total,
|
||||
Items: roles,
|
||||
}, nil
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type updateOpts struct {
|
||||
RoleID string `src:"path@role_id" desc:"角色ID"` // 角色 ID
|
||||
Name *string `json:"name" src:"json" desc:"角色名称"` // 可选,角色名称
|
||||
Des *string `json:"des" src:"json" desc:"角色描述"` // 可选,角色描述
|
||||
}
|
||||
|
||||
var _ = Router.Patch("/{role_id}", "更新角色", updateRole)
|
||||
|
||||
func updateRole(x *vigo.X, opts *updateOpts) (*models.Role, error) {
|
||||
// 查找角色
|
||||
role := &models.Role{}
|
||||
if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil {
|
||||
return nil, vigo.NewError("role not found").WithCode(404)
|
||||
}
|
||||
|
||||
// 准备更新字段
|
||||
updates := map[string]interface{}{}
|
||||
|
||||
if opts.Name != nil {
|
||||
updates["name"] = *opts.Name
|
||||
}
|
||||
|
||||
if opts.Des != nil {
|
||||
updates["des"] = *opts.Des
|
||||
}
|
||||
|
||||
// 如果没有更新字段,直接返回
|
||||
if len(updates) == 0 {
|
||||
return role, nil
|
||||
}
|
||||
|
||||
// 执行更新
|
||||
if err := cfg.DB().Model(role).Updates(updates).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重新查询以获取最新数据
|
||||
if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
//
|
||||
// init.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
//
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package sms
|
||||
|
||||
import "github.com/veypi/vigo"
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/libs/sms_providers"
|
||||
"github.com/veypi/vbase/libs/utils"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
"github.com/veypi/vigo/contrib/limiter"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SendCodeRequest 发送验证码请求
|
||||
type SendCodeRequest struct {
|
||||
Phone string `json:"phone" src:"json" desc:"手机号"`
|
||||
Region string `json:"region" src:"json" desc:"区域"`
|
||||
Purpose string `json:"purpose" src:"json" desc:"用途"`
|
||||
TemplateID string `json:"template_id" src:"json" desc:"模板ID"`
|
||||
}
|
||||
|
||||
// SendCodeResponse 发送验证码响应
|
||||
type SendCodeResponse struct {
|
||||
ID string `json:"id"`
|
||||
Phone string `json:"phone"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
Interval int `json:"interval"` // 下次可发送间隔(秒)
|
||||
}
|
||||
|
||||
var _ = Router.Post("/", "发送验证码", vigo.SkipBefore, limiter.NewAdvancedRequestLimiter(time.Minute*3, 5, time.Second*30).Limit, sendCode)
|
||||
|
||||
// SendCode 发送验证码
|
||||
func sendCode(x *vigo.X, req *SendCodeRequest) (*SendCodeResponse, error) {
|
||||
// 1. 验证手机号
|
||||
normalizedPhone := utils.NormalizePhoneNumber(req.Phone)
|
||||
if !utils.ValidatePhoneNumber(normalizedPhone) {
|
||||
return nil, fmt.Errorf("invalid phone number: %s", req.Phone)
|
||||
}
|
||||
|
||||
// 2. 检查区域配置
|
||||
provider, err := sms_providers.GetSMSProvider(req.Region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 检查发送频率限制
|
||||
if err := CheckSendInterval(normalizedPhone, req.Region, req.Purpose); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. 检查每日发送次数限制
|
||||
if err := CheckDailyLimit(normalizedPhone, req.Region); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 生成验证码
|
||||
code, err := utils.GenerateCode(cfg.Config.SMS.Global.CodeLength)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate code: %w", err)
|
||||
}
|
||||
|
||||
// 6. 创建验证码记录
|
||||
smsCode := &models.SMSCode{
|
||||
Phone: normalizedPhone,
|
||||
Code: code,
|
||||
Region: req.Region,
|
||||
Purpose: req.Purpose,
|
||||
Status: models.CodeStatusPending,
|
||||
ExpiresAt: time.Now().Add(cfg.Config.SMS.Global.CodeExpiry),
|
||||
}
|
||||
|
||||
// 7. 发送短信
|
||||
sendReq := &sms_providers.SendSMSRequest{
|
||||
Phone: normalizedPhone,
|
||||
TemplateID: req.TemplateID,
|
||||
Region: req.Region,
|
||||
Purpose: req.Purpose,
|
||||
Params: map[string]string{
|
||||
"code": code,
|
||||
},
|
||||
}
|
||||
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Create(smsCode).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
smsID, err := provider.SendSMS(x.Context(), sendReq)
|
||||
LogSMS(normalizedPhone, req.Region, provider.GetName(), smsID, err)
|
||||
if err != nil || smsID == "" {
|
||||
return fmt.Errorf("failed to send sms: %v", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SendCodeResponse{
|
||||
ID: smsCode.ID,
|
||||
Phone: smsCode.Phone,
|
||||
ExpiresAt: smsCode.ExpiresAt,
|
||||
Interval: int(cfg.Config.SMS.Global.SendInterval.Seconds()),
|
||||
}, nil
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
//
|
||||
// utils.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
//
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package sms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// checkDailyLimit 检查每日发送次数限制
|
||||
func CheckDailyLimit(phone, region string) error {
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
tomorrow := today.Add(24 * time.Hour)
|
||||
|
||||
var count int64
|
||||
err := cfg.DB().Model(&models.SMSCode{}).
|
||||
Where("phone = ? AND region = ? AND created_at >= ? AND created_at < ?",
|
||||
phone, region, today, tomorrow).
|
||||
Count(&count).Error
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check daily limit: %w", err)
|
||||
}
|
||||
|
||||
if count >= int64(cfg.Config.SMS.Global.MaxDailyCount) {
|
||||
return fmt.Errorf("daily sms limit exceeded")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkSendInterval 检查发送间隔
|
||||
func CheckSendInterval(phone, region, purpose string) error {
|
||||
var lastCode models.SMSCode
|
||||
err := cfg.DB().Where("phone = ? AND region = ? AND purpose = ?", phone, region, purpose).
|
||||
Order("created_at DESC").
|
||||
First(&lastCode).Error
|
||||
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return fmt.Errorf("failed to check send interval: %w", err)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
timeSinceLastSend := time.Since(lastCode.CreatedAt)
|
||||
if timeSinceLastSend < cfg.Config.SMS.Global.SendInterval {
|
||||
remaining := cfg.Config.SMS.Global.SendInterval - timeSinceLastSend
|
||||
return fmt.Errorf("please wait %d seconds before sending again", int(remaining.Seconds()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// logSMS 记录短信发送日志
|
||||
func LogSMS(phone, region, provider, messageID string, err error) {
|
||||
log := &models.SMSLog{
|
||||
Phone: phone,
|
||||
Region: region,
|
||||
Provider: provider,
|
||||
MessageID: messageID,
|
||||
Status: "ok",
|
||||
}
|
||||
if err != nil {
|
||||
log.Status = "failed"
|
||||
log.Error = err.Error()
|
||||
}
|
||||
cfg.DB().Create(log)
|
||||
}
|
||||
|
||||
// CleanupExpiredCodes 清理过期的验证码
|
||||
func CleanupExpiredCodes() error {
|
||||
result := cfg.DB().Model(&models.SMSCode{}).
|
||||
Where("status = ? AND expires_at < ?", models.CodeStatusPending, time.Now()).
|
||||
Update("status", models.CodeStatusExpired)
|
||||
|
||||
if result.Error != nil {
|
||||
return fmt.Errorf("failed to cleanup expired codes: %w", result.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
//
|
||||
// validate.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
//
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package sms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/libs/utils"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func VerifyCode(Phone, Code, Region, Purpose string) error {
|
||||
normalizedPhone := utils.NormalizePhoneNumber(Phone)
|
||||
|
||||
// 1. 查找最新的待验证码
|
||||
var smsCode models.SMSCode
|
||||
err := cfg.DB().Where("phone = ? AND region = ? AND purpose = ? AND status = ?",
|
||||
normalizedPhone, Region, Purpose, models.CodeStatusPending).
|
||||
Order("created_at DESC").
|
||||
First(&smsCode).Error
|
||||
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return vigo.NewError("verification code not found or already used").WithCode(404)
|
||||
}
|
||||
return vigo.NewError("failed to query sms code").WithError(err)
|
||||
}
|
||||
|
||||
// 2. 检查验证码是否过期
|
||||
if smsCode.IsExpired() {
|
||||
cfg.DB().Model(&smsCode).Updates(map[string]any{
|
||||
"status": models.CodeStatusExpired,
|
||||
})
|
||||
return fmt.Errorf("verification code has expired")
|
||||
}
|
||||
|
||||
// 3. 检查是否已达到最大尝试次数
|
||||
if !smsCode.CanRetry(cfg.Config.SMS.Global.MaxAttempts) {
|
||||
cfg.DB().Model(&smsCode).Updates(map[string]any{
|
||||
"status": models.CodeStatusFailed,
|
||||
})
|
||||
return fmt.Errorf("verification failed too many times")
|
||||
}
|
||||
|
||||
// 4. 验证码不匹配
|
||||
if smsCode.Code != Code {
|
||||
cfg.DB().Model(&smsCode).Updates(map[string]any{
|
||||
"attempts": smsCode.Attempts + 1,
|
||||
})
|
||||
|
||||
remaining := cfg.Config.SMS.Global.MaxAttempts - smsCode.Attempts - 1
|
||||
return fmt.Errorf("verification code incorrect, %d attempts remaining", remaining)
|
||||
}
|
||||
|
||||
// 5. 验证成功
|
||||
now := time.Now()
|
||||
cfg.DB().Model(&smsCode).Updates(map[string]any{
|
||||
"status": models.CodeStatusUsed,
|
||||
"used_at": &now,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type patchOpts struct {
|
||||
ID string `json:"id" src:"path@token_id" desc:"令牌ID"`
|
||||
ExpiredAt *time.Time `json:"expired_at" src:"json" desc:"过期时间"`
|
||||
OverPerm *string `json:"over_perm" src:"json" desc:"覆盖权限"`
|
||||
}
|
||||
|
||||
var _ = Router.Patch("/{token_id}", "更新 Token", tokenPatch)
|
||||
|
||||
func tokenPatch(x *vigo.X, opts *patchOpts) (*models.Token, error) {
|
||||
data := &models.Token{}
|
||||
|
||||
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.ExpiredAt != nil {
|
||||
optsMap["expired_at"] = opts.ExpiredAt
|
||||
}
|
||||
if opts.OverPerm != nil {
|
||||
optsMap["over_perm"] = opts.OverPerm
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
type deleteOpts struct {
|
||||
ID string `src:"path@token_id" desc:"令牌ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{token_id}", "删除 Token", tokenDelete)
|
||||
|
||||
func tokenDelete(x *vigo.X, opts *deleteOpts) (*models.Token, error) {
|
||||
data := &models.Token{}
|
||||
err := cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
return data, err
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
//
|
||||
// get.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-09 17:24
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type getOpts struct {
|
||||
ID string `json:"id" src:"path@token_id" desc:"令牌ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{token_id}", "获取 Token 详情", tokenGet)
|
||||
|
||||
func tokenGet(x *vigo.X, opts *getOpts) (*models.Token, error) {
|
||||
data := &models.Token{}
|
||||
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
type listOpts struct {
|
||||
Limit int `json:"limit" src:"query" desc:"限制数量"`
|
||||
UserID string `json:"user_id" gorm:"index;type:varchar(36)" src:"query" desc:"用户ID"`
|
||||
AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"query" desc:"应用ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/", "获取 Token 列表", tokenList)
|
||||
|
||||
func tokenList(x *vigo.X, opts *listOpts) ([]*models.Token, error) {
|
||||
data := make([]*models.Token, 0, 10)
|
||||
|
||||
query := cfg.DB()
|
||||
if opts.UserID != "" {
|
||||
query = query.Where("user_id = ?", opts.UserID)
|
||||
}
|
||||
if opts.AppID != "" {
|
||||
query = query.Where("app_id = ?", opts.AppID)
|
||||
}
|
||||
err := query.Limit(opts.Limit).Find(&data).Error
|
||||
return data, err
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-24 22:37:12
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
//
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
@ -1,114 +0,0 @@
|
||||
//
|
||||
// create.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-06 15:05
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/veypi/vbase/api/sms"
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/libs/utils"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var _ = Router.Post("/", "用户注册", vigo.SkipBefore, publicLimits, userPost)
|
||||
|
||||
type postOpts struct {
|
||||
Username string `json:"username" gorm:"varchar(100);unique;default:not null" src:"json" desc:"用户名"`
|
||||
Code string `json:"code" gorm:"varchar(128)" src:"json" desc:"授权码/密码"`
|
||||
Phone string `json:"phone" gorm:"varchar(50);unique;default:null" src:"json" desc:"手机号"`
|
||||
VerifyCode string `json:"verify_code" src:"json" desc:"验证码"`
|
||||
Region string `json:"region" src:"json" desc:"区域"`
|
||||
|
||||
Nickname *string `json:"nickname" src:"json" desc:"昵称"`
|
||||
Icon *string `json:"icon" src:"json" desc:"头像"`
|
||||
Email *string `json:"email" gorm:"varchar(20);unique;default:null" src:"json" desc:"邮箱"`
|
||||
}
|
||||
|
||||
func userPost(x *vigo.X, opts *postOpts) (*models.User, error) {
|
||||
data := &models.User{}
|
||||
|
||||
if cfg.Config.SMS.Enable {
|
||||
data.Phone = opts.Region + opts.Phone
|
||||
data.Region = opts.Region
|
||||
err := sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signup")
|
||||
if err != nil {
|
||||
return nil, vigo.ErrArgInvalid.WithArgs("verify code").WithString(err.Error())
|
||||
}
|
||||
}
|
||||
data.Username = opts.Username
|
||||
data.Code = opts.Code
|
||||
data.Salt = utils.RandSeq(16)
|
||||
if len(data.Username) < 2 {
|
||||
return nil, vigo.ErrArgInvalid.WithArgs("username length")
|
||||
}
|
||||
code, err := base64.URLEncoding.DecodeString(opts.Code)
|
||||
if err != nil || len(code) < 8 {
|
||||
return nil, vigo.ErrArgInvalid.WithArgs("code")
|
||||
}
|
||||
code = utils.PKCS7Padding(code, 32)
|
||||
// We need ID for encryption, but it's not generated yet.
|
||||
// We can generate it manually here since vigo.Model doesn't auto-generate it before Create
|
||||
data.ID = strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
|
||||
data.Code, err = utils.AesEncrypt([]byte(data.ID), code, []byte(data.Salt))
|
||||
if err != nil {
|
||||
return nil, vigo.ErrArgInvalid.WithArgs("code")
|
||||
}
|
||||
ncode, err := utils.AesDecrypt([]byte(data.Code), code, []byte(data.Salt))
|
||||
if err != nil || string(ncode) != data.ID {
|
||||
return nil, vigo.ErrInternalServer.WithString("code decrypt failed")
|
||||
}
|
||||
if opts.Nickname != nil {
|
||||
data.Nickname = *opts.Nickname
|
||||
}
|
||||
if opts.Icon != nil {
|
||||
data.Icon = *opts.Icon
|
||||
} else {
|
||||
data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))
|
||||
}
|
||||
if opts.Email != nil {
|
||||
data.Email = *opts.Email
|
||||
}
|
||||
data.Status = 1
|
||||
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Create(data).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app := &models.App{}
|
||||
err = tx.Where("id = ?", cfg.Config.ID).First(app).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := "ok"
|
||||
switch app.Typ {
|
||||
case "private":
|
||||
return vigo.ErrNotPermitted.WithArgs("not enable register")
|
||||
case "apply":
|
||||
status = "applying"
|
||||
case "public":
|
||||
}
|
||||
if app.Typ != "public" {
|
||||
}
|
||||
|
||||
return tx.Create(&models.AppUser{
|
||||
UserID: data.ID,
|
||||
AppID: cfg.Config.ID,
|
||||
Status: status,
|
||||
}).Error
|
||||
})
|
||||
return data, err
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 16:28:13
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
// Auto generated by OneBD. DO NOT EDIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/api/user/role"
|
||||
"github.com/veypi/vbase/libs/auth"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
|
||||
func init() {
|
||||
Router.Extend("/{user_id}/user_role", role.Router).Use(auth.Check("user", "", 4))
|
||||
}
|
||||
@ -1,114 +0,0 @@
|
||||
//
|
||||
// login.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-12 17:35
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/veypi/vbase/api/sms"
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/libs/auth"
|
||||
"github.com/veypi/vbase/libs/utils"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
"github.com/veypi/vigo/contrib/limiter"
|
||||
"github.com/veypi/vigo/logv"
|
||||
)
|
||||
|
||||
var publicLimits = limiter.NewAdvancedRequestLimiter(time.Minute*5, 20, time.Second*3, nil).Limit
|
||||
|
||||
var _ = Router.Post("/login", "用户登录",
|
||||
vigo.SkipBefore, publicLimits,
|
||||
userLogin)
|
||||
|
||||
type loginOpts struct {
|
||||
UserName *string `json:"username" src:"json" desc:"用户名"`
|
||||
Code *string `json:"code" src:"json" desc:"密码/授权码"`
|
||||
|
||||
VerifyCode *string `json:"verify_code" src:"json" desc:"验证码"`
|
||||
Region *string `json:"region" src:"json" desc:"区域"`
|
||||
Phone *string `json:"phone" src:"json" desc:"手机号"`
|
||||
|
||||
Email *string `json:"email" src:"json" desc:"邮箱"`
|
||||
Type *string `json:"type" src:"json" desc:"登录类型"`
|
||||
AppID *string `json:"app_id" src:"json" desc:"应用ID"`
|
||||
Device *string `json:"device" src:"json" desc:"设备信息"`
|
||||
}
|
||||
|
||||
func userLogin(x *vigo.X, opts *loginOpts) (string, error) {
|
||||
// Implement login logic here
|
||||
// For example, validate user credentials and return a token
|
||||
user := &models.User{}
|
||||
query := cfg.DB()
|
||||
typ := ""
|
||||
if opts.Type != nil {
|
||||
typ = *opts.Type
|
||||
}
|
||||
switch typ {
|
||||
case "phone":
|
||||
query = query.Where("phone = ?", *opts.Region+*opts.Phone)
|
||||
case "email":
|
||||
query = query.Where("email = ?", *opts.Email)
|
||||
default:
|
||||
query = query.Where("username = ?", *opts.UserName)
|
||||
}
|
||||
err := query.First(user).Error
|
||||
if err != nil {
|
||||
return "", vigo.ErrNotFound
|
||||
}
|
||||
logv.Info().Str("user", user.ID).Msg("login")
|
||||
if opts.VerifyCode != nil {
|
||||
err = sms.VerifyCode(*opts.Phone, *opts.VerifyCode, *opts.Region, "signin")
|
||||
if err != nil {
|
||||
logv.Warn().Msgf("verify code: %v", err)
|
||||
return "", vigo.ErrNotAuthorized.WithError(err)
|
||||
}
|
||||
} else {
|
||||
code, err := base64.URLEncoding.DecodeString(*opts.Code)
|
||||
if err != nil {
|
||||
return "", vigo.ErrArgInvalid.WithArgs("code")
|
||||
}
|
||||
|
||||
ncode, err := utils.AesDecrypt([]byte(user.Code), utils.PKCS7Padding(code, 32), []byte(user.Salt))
|
||||
if err != nil || string(ncode) != user.ID {
|
||||
return "", vigo.ErrNotAuthorized
|
||||
}
|
||||
}
|
||||
aid := cfg.Config.ID
|
||||
if opts.AppID != nil && *opts.AppID != "" {
|
||||
aid = *opts.AppID
|
||||
}
|
||||
|
||||
data := &models.Token{}
|
||||
|
||||
data.UserID = user.ID
|
||||
data.AppID = aid
|
||||
data.ExpiredAt = time.Now().Add(time.Hour * 72)
|
||||
if opts.Device != nil {
|
||||
data.Device = *opts.Device
|
||||
}
|
||||
|
||||
err = cfg.DB().Create(data).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
claim := &auth.Claims{}
|
||||
claim.ID = data.ID
|
||||
claim.UID = user.ID
|
||||
claim.AID = aid
|
||||
claim.Name = user.Username
|
||||
claim.Icon = user.Icon
|
||||
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
|
||||
claim.IssuedAt = jwt.NewNumericDate(time.Now())
|
||||
claim.Issuer = cfg.Config.ID
|
||||
|
||||
return auth.GenJwt(claim)
|
||||
}
|
||||
@ -1,35 +0,0 @@
|
||||
//
|
||||
// crud.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-06 15:12
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var _ = Router.Post("/", "创建用户角色", userRolePost)
|
||||
|
||||
type postOpts struct {
|
||||
UserID string `json:"user_id" src:"path" desc:"用户ID"`
|
||||
RoleID string `json:"role_id" src:"json" desc:"角色ID"`
|
||||
AppID string `json:"app_id" src:"json" desc:"应用ID"`
|
||||
Status string `json:"status" src:"json" desc:"状态"`
|
||||
}
|
||||
|
||||
func userRolePost(x *vigo.X, opts *postOpts) (*models.UserRole, error) {
|
||||
data := &models.UserRole{}
|
||||
|
||||
data.UserID = opts.UserID
|
||||
data.RoleID = opts.RoleID
|
||||
data.AppID = opts.AppID
|
||||
data.Status = opts.Status
|
||||
err := cfg.DB().Create(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
//
|
||||
// del.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-06 15:24
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type deleteIDReq struct {
|
||||
ID string `src:"path@id" desc:"记录ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{id}", "删除用户角色", userRoleDelete)
|
||||
|
||||
func userRoleDelete(x *vigo.X, req *deleteIDReq) (*models.UserRole, error) {
|
||||
data := &models.UserRole{}
|
||||
err := cfg.DB().Where("id = ?", req.ID).Delete(data).Error
|
||||
return data, err
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
//
|
||||
// get.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-06 15:21
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type getIDReq struct {
|
||||
ID string `src:"path@id" desc:"记录ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{id}", "获取用户角色详情", userRoleGet)
|
||||
|
||||
func userRoleGet(x *vigo.X, req *getIDReq) (*models.UserRole, error) {
|
||||
data := &models.UserRole{}
|
||||
err := cfg.DB().Where("id = ?", req.ID).First(data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
type listOpts struct {
|
||||
UserID *string `json:"user_id" src:"path@user_id" desc:"用户ID"`
|
||||
RoleID *string `json:"role_id" src:"query" desc:"角色ID"`
|
||||
AppID *string `json:"app_id" src:"query" desc:"应用ID"`
|
||||
Status *string `json:"status" src:"query" desc:"状态"`
|
||||
}
|
||||
|
||||
var _ = Router.Get("/", "获取用户角色列表", userRoleList)
|
||||
|
||||
type userRoleListItem struct {
|
||||
models.UserRole
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Icon string `json:"icon"`
|
||||
RoleName string `json:"role_name"`
|
||||
}
|
||||
|
||||
func userRoleList(x *vigo.X, opts *listOpts) ([]*userRoleListItem, error) {
|
||||
data := make([]*userRoleListItem, 0, 10)
|
||||
// data := make([]*M.UserRole, 0, 10)
|
||||
|
||||
query := cfg.DB().Debug().Table("user_roles").Select("user_roles.*,users.username,users.nickname,users.icon,roles.name as role_name").
|
||||
Joins("JOIN users ON users.id = user_roles.user_id").
|
||||
Joins("JOIN roles ON roles.id = user_roles.role_id")
|
||||
if opts.UserID != nil && *opts.UserID != "-" {
|
||||
query = query.Where("user_id LIKE ?", opts.UserID)
|
||||
}
|
||||
if opts.RoleID != nil {
|
||||
query = query.Where("role_id LIKE ?", opts.RoleID)
|
||||
}
|
||||
if opts.AppID != nil {
|
||||
query = query.Where("app_id LIKE ?", opts.AppID)
|
||||
}
|
||||
if opts.Status != nil {
|
||||
query = query.Where("status LIKE ?", opts.Status)
|
||||
}
|
||||
err := query.Scan(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
//
|
||||
// init.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-06 15:12
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package role
|
||||
|
||||
import "github.com/veypi/vigo"
|
||||
|
||||
var Router = vigo.NewRouter()
|
||||
@ -1,36 +0,0 @@
|
||||
//
|
||||
// crud.go
|
||||
// Copyright (C) 2025 veypi <i@veypi.com>
|
||||
// 2025-05-06 15:12
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package role
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type patchOpts struct {
|
||||
ID string `json:"id" src:"path@id" desc:"记录ID"`
|
||||
Status *string `json:"status" src:"json" desc:"状态"`
|
||||
}
|
||||
|
||||
var _ = Router.Patch("/{id}", "更新用户角色", userRolePatch)
|
||||
|
||||
func userRolePatch(x *vigo.X, opts *patchOpts) (*models.UserRole, error) {
|
||||
data := &models.UserRole{}
|
||||
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]any)
|
||||
if opts.Status != nil {
|
||||
optsMap["status"] = opts.Status
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vbase/libs/auth"
|
||||
"github.com/veypi/vbase/models"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
type userIDReq struct {
|
||||
UserID string `src:"path@user_id" desc:"用户ID"`
|
||||
}
|
||||
|
||||
var _ = Router.Delete("/{user_id}", "删除用户", auth.Check("user", "user_id", auth.DoDelete, checkOwner), userDelete)
|
||||
|
||||
func userDelete(x *vigo.X, req *userIDReq) (*models.User, error) {
|
||||
data := &models.User{}
|
||||
err := cfg.DB().Where("id = ?", req.UserID).Delete(data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
var _ = Router.Get("/{user_id}", "获取用户详情", auth.Check("user", "user_id", auth.DoRead, checkOwner), userGet)
|
||||
|
||||
func checkOwner(x *vigo.X, data any) bool {
|
||||
u, ok1 := data.(*models.User)
|
||||
return ok1 && u.ID == x.PathParams.Get("user_id")
|
||||
}
|
||||
|
||||
func userGet(x *vigo.X, req *userIDReq) (*models.User, error) {
|
||||
data := &models.User{}
|
||||
err := cfg.DB().Where("id = ?", req.UserID).First(data).Error
|
||||
return data, err
|
||||
}
|
||||
|
||||
type listReq struct {
|
||||
Page int `src:"query" json:"page" desc:"页码"`
|
||||
PageSize int `src:"query" json:"page_size" desc:"每页数量"`
|
||||
Keyword string `src:"query" json:"keyword" desc:"搜索关键词"`
|
||||
}
|
||||
|
||||
type listResp struct {
|
||||
Items []*models.User `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
func userList(x *vigo.X, req *listReq) (*listResp, error) {
|
||||
if req.Page <= 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize <= 0 || req.PageSize > 100 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
|
||||
var total int64
|
||||
query := cfg.DB().Model(&models.User{})
|
||||
|
||||
if req.Keyword != "" {
|
||||
query = query.Where("username LIKE ? OR nickname LIKE ? OR email LIKE ?", "%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []*models.User
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
if err := query.Offset(offset).Limit(req.PageSize).Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalPages := int((total + int64(req.PageSize) - 1) / int64(req.PageSize))
|
||||
|
||||
return &listResp{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ = Router.Get("/", "用户列表", auth.Check("user", "", auth.DoUpdate), userList)
|
||||
|
||||
var _ = Router.Patch("/{user_id}", "更新用户", auth.Check("user", "user_id", auth.DoUpdate, checkOwner), userPatch)
|
||||
|
||||
type patchOpts struct {
|
||||
ID string `json:"id" src:"path@user_id" desc:"用户ID"`
|
||||
Nickname *string `json:"nickname" src:"json" desc:"昵称"`
|
||||
Icon *string `json:"icon" src:"json" desc:"头像"`
|
||||
|
||||
Username *string `json:"username" src:"json" desc:"用户名"`
|
||||
Email *string `json:"email" src:"json" desc:"邮箱"`
|
||||
Phone *string `json:"phone" src:"json" desc:"手机号"`
|
||||
Status *uint `json:"status" src:"json" desc:"状态"`
|
||||
}
|
||||
|
||||
func userPatch(x *vigo.X, opts *patchOpts) (*models.User, error) {
|
||||
data := &models.User{}
|
||||
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, vigo.ErrNotFound
|
||||
}
|
||||
|
||||
optsMap := make(map[string]any)
|
||||
if opts.Username != nil {
|
||||
optsMap["username"] = opts.Username
|
||||
}
|
||||
if opts.Nickname != nil {
|
||||
optsMap["nickname"] = opts.Nickname
|
||||
}
|
||||
if opts.Icon != nil {
|
||||
optsMap["icon"] = opts.Icon
|
||||
}
|
||||
if opts.Email != nil {
|
||||
optsMap["email"] = opts.Email
|
||||
}
|
||||
if opts.Phone != nil {
|
||||
optsMap["phone"] = opts.Phone
|
||||
}
|
||||
if opts.Status != nil {
|
||||
optsMap["status"] = opts.Status
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
host: 0.0.0.0
|
||||
port: 4001
|
||||
loggerpath: ""
|
||||
loggerlevel: debug
|
||||
dsn: root:123456@tcp(127.0.0.1:3306)/oa?charset=utf8&parseTime=True&loc=Local
|
||||
db: mysql
|
||||
id: "ascc2fdf2"
|
||||
key: "sasdsdsasd123sdf"
|
||||
@ -1,18 +0,0 @@
|
||||
//
|
||||
// errors.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-08-01 15:40
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package errs
|
||||
|
||||
import "github.com/veypi/vigo"
|
||||
|
||||
var (
|
||||
AuthNotFound = vigo.NewError("auth not found").WithCode(40100)
|
||||
AuthFailed = vigo.NewError("auth failed").WithCode(40101)
|
||||
AuthExpired = vigo.NewError("auth expired").WithCode(40102)
|
||||
AuthInvalid = vigo.NewError("auth invalid").WithCode(40103)
|
||||
AuthNoPerm = vigo.NewError("auth no permission").WithCode(40104)
|
||||
)
|
||||
@ -1,68 +0,0 @@
|
||||
//
|
||||
// access.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 19:37
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type AuthLevel = int
|
||||
|
||||
const (
|
||||
DoNone = 0
|
||||
Do = 1
|
||||
DoRead = 1
|
||||
DoCreate = 2
|
||||
DoUpdate = 3
|
||||
DoDelete = 4
|
||||
DoAll = 5
|
||||
)
|
||||
|
||||
type Access []*struct {
|
||||
Name string `json:"name"`
|
||||
TID string `json:"tid"`
|
||||
Level AuthLevel `json:"level"`
|
||||
}
|
||||
|
||||
func (a *Access) Check(target string, tid string, l AuthLevel) bool {
|
||||
if l == DoNone {
|
||||
return true
|
||||
}
|
||||
for _, line := range *a {
|
||||
if line.Name == target && line.Level >= l {
|
||||
if line.TID == "" || line.TID == tid {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func (a *Access) CheckPrefix(target string, tid string, l AuthLevel) bool {
|
||||
if l == DoNone {
|
||||
return true
|
||||
}
|
||||
for _, line := range *a {
|
||||
if line.Name == target && line.Level >= l {
|
||||
if line.TID == "" || strings.HasPrefix(tid, line.TID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
UID string `json:"uid"`
|
||||
AID string `json:"aid"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Access Access `json:"access"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
@ -1,117 +0,0 @@
|
||||
//
|
||||
// jwt.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 18:28
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/veypi/vbase/cfg"
|
||||
"github.com/veypi/vigo"
|
||||
)
|
||||
|
||||
var (
|
||||
AuthNotFound = vigo.NewError("auth not found").WithCode(40100)
|
||||
AuthFailed = vigo.NewError("auth failed").WithCode(40101)
|
||||
AuthExpired = vigo.NewError("auth expired").WithCode(40102)
|
||||
AuthInvalid = vigo.NewError("auth invalid").WithCode(40103)
|
||||
AuthNoPerm = vigo.NewError("auth no permission").WithCode(40104)
|
||||
)
|
||||
|
||||
func GenJwt(claim *Claims) (string, error) {
|
||||
return GenJwtWithKey(claim, cfg.Config.Key)
|
||||
}
|
||||
|
||||
func GenJwtWithKey(claim *Claims, key string) (string, error) {
|
||||
if claim.ExpiresAt == nil {
|
||||
claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Hour))
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
|
||||
return token.SignedString([]byte(key))
|
||||
}
|
||||
|
||||
func ParseJwt(tokenString string, keys ...string) (*Claims, error) {
|
||||
key := cfg.Config.Key
|
||||
if len(keys) > 0 {
|
||||
key = keys[0]
|
||||
}
|
||||
claims := &Claims{}
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return []byte(key), nil
|
||||
})
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return nil, AuthExpired
|
||||
}
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
return nil, AuthInvalid
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func CheckJWT(x *vigo.X) (*Claims, error) {
|
||||
authHeader := x.Request.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
authHeader = x.Request.URL.Query().Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return nil, AuthNotFound
|
||||
}
|
||||
}
|
||||
// Token is typically in the format "Bearer <token>"
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
|
||||
// Parse the token
|
||||
claims, err := ParseJwt(tokenString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x.Set("token", claims)
|
||||
x.Set("uid", claims.UID)
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
type CustomCheckFunc = func(x *vigo.X, data any) bool
|
||||
|
||||
func Check(target string, pid string, l AuthLevel, funcs ...CustomCheckFunc) func(x *vigo.X, data any) (any, error) {
|
||||
return func(x *vigo.X, data any) (any, error) {
|
||||
var err error
|
||||
claims, ok := x.Get("token").(*Claims)
|
||||
if !ok {
|
||||
claims, err = CheckJWT(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
tid := ""
|
||||
if strings.HasPrefix(pid, "@") {
|
||||
tid, _ = x.Get(pid[1:]).(string)
|
||||
}
|
||||
if strings.HasPrefix(pid, "{") && strings.HasSuffix(pid, "}") {
|
||||
tid = x.PathParams.Get(pid[1 : len(pid)-1])
|
||||
}
|
||||
if strings.HasPrefix(pid, ":") {
|
||||
tid = x.PathParams.Get(pid[1:])
|
||||
}
|
||||
if !claims.Access.Check(target, tid, l) {
|
||||
err = AuthNoPerm
|
||||
}
|
||||
for _, fn := range funcs {
|
||||
if fn(x, data) {
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
"github.com/veypi/vigo/logv"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const AUSTATUS_OK = "ok"
|
||||
const AUSTATUS_NO = "no"
|
||||
const AUSTATUS_APPLYING = "applying"
|
||||
const AUSTATUS_REJECT = "reject"
|
||||
|
||||
type App struct {
|
||||
vigo.Model
|
||||
Name string `json:"name" src:"json" desc:"应用名称"`
|
||||
Icon string `json:"icon" src:"json" desc:"图标"`
|
||||
Des string `json:"des" src:"json" desc:"描述"`
|
||||
Typ string `json:"typ" gorm:"default:public" src:"json" desc:"类型"`
|
||||
Status string `json:"status" gorm:"default:ok" src:"json" desc:"状态"`
|
||||
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"初始角色ID"`
|
||||
InitRole *Role `json:"init_role" gorm:"foreignKey:InitRoleID;references:ID"`
|
||||
InitUrl string `json:"init_url" src:"json" desc:"初始URL"`
|
||||
UserCount uint `json:"user_count" default:"0"`
|
||||
Key string `json:"-"`
|
||||
}
|
||||
|
||||
type AppUser struct {
|
||||
vigo.Model
|
||||
AppID string `json:"app_id" src:"path" desc:"应用ID"`
|
||||
App *App `json:"app" gorm:"foreignKey:AppID;references:ID"`
|
||||
|
||||
UserID string `json:"user_id" src:"json" desc:"用户ID"`
|
||||
User *User `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
|
||||
Status string `json:"status" src:"json" desc:"状态"`
|
||||
}
|
||||
|
||||
func (m *AppUser) onOk(tx *gorm.DB) (err error) {
|
||||
app := &App{}
|
||||
logv.AssertError(tx.Where("id = ?", m.AppID).First(app).Error)
|
||||
if app.InitRoleID != nil {
|
||||
urList := make([]*UserRole, 0, 2)
|
||||
logv.AssertError(tx.Debug().Where("app_id = ? AND user_id = ?", m.AppID, m.UserID).Find(&urList).Error)
|
||||
if len(urList) == 0 {
|
||||
return tx.Create(&UserRole{
|
||||
AppID: m.AppID,
|
||||
UserID: m.UserID,
|
||||
RoleID: *app.InitRoleID,
|
||||
Status: "ok",
|
||||
}).Error
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AppUser) AfterCreate(tx *gorm.DB) error {
|
||||
if m.Status == AUSTATUS_OK {
|
||||
logv.AssertError(m.onOk(tx))
|
||||
}
|
||||
return tx.Model(&App{}).Where("id = ?", m.AppID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error
|
||||
}
|
||||
|
||||
func (m *AppUser) AfterUpdate(tx *gorm.DB) error {
|
||||
if m.Status == AUSTATUS_OK {
|
||||
return m.onOk(tx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
vigo.Model
|
||||
AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"`
|
||||
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
|
||||
Name string `json:"name" src:"json" desc:"名称"`
|
||||
Des string `json:"des" src:"json" desc:"描述"`
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
vigo.Model
|
||||
AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"`
|
||||
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
|
||||
Name string `json:"name" src:"json" desc:"名称"`
|
||||
Des string `json:"des" src:"json" desc:"描述"`
|
||||
UserCount uint `json:"user_count" default:"0"`
|
||||
Access []*Access `json:"-"`
|
||||
}
|
||||
|
||||
type Access struct {
|
||||
vigo.Model
|
||||
AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"path@app_id" desc:"应用ID"`
|
||||
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
|
||||
|
||||
UserID *string `json:"user_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"用户ID"`
|
||||
User *User `json:"-" gorm:"foreignKey:UserID;references:ID"`
|
||||
|
||||
RoleID *string `json:"role_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"角色ID"`
|
||||
Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"`
|
||||
|
||||
ResourceID *string `json:"resource_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"资源ID"`
|
||||
Resource *Resource `json:"-" gorm:"foreignKey:ResourceID;references:ID"`
|
||||
|
||||
Name string `json:"name" src:"json" desc:"名称"`
|
||||
TID string `json:"tid" src:"json" desc:"资源ID"`
|
||||
Level uint `json:"level" src:"json" desc:"级别"`
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/veypi/vigo"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// salt for user user password gen aes code
|
||||
// salt 32 hex / 16 byte / 128 bit
|
||||
// code 64 hex / 32 byte / 256 bit
|
||||
type User struct {
|
||||
vigo.Model
|
||||
Username string `json:"username" gorm:"type:varchar(100);unique;default:not null" src:"json" desc:"用户名"`
|
||||
Nickname string `json:"nickname" gorm:"type:varchar(100)" src:"json" desc:"昵称"`
|
||||
Icon string `json:"icon" src:"json" desc:"头像"`
|
||||
|
||||
Email string `json:"email" gorm:"unique;type:varchar(64);null;default:null" src:"json" desc:"邮箱"`
|
||||
Phone string `json:"phone" gorm:"type:varchar(64);unique;null;default:null" src:"json" desc:"手机号"`
|
||||
Region string `json:"region" gorm:"type:varchar(32);default:null" src:"json" desc:"地区"`
|
||||
|
||||
Status uint `json:"status" src:"json" desc:"状态"`
|
||||
|
||||
Salt string `json:"-" gorm:"type:varchar(32)"`
|
||||
Code string `json:"-" gorm:"type:varchar(64)" src:"json" desc:"代码"`
|
||||
}
|
||||
|
||||
type UserRole struct {
|
||||
vigo.Model
|
||||
UserID string `json:"user_id" src:"path@user_id" desc:"用户ID"`
|
||||
User *User `json:"-" gorm:"foreignKey:UserID;references:ID"`
|
||||
|
||||
RoleID string `json:"role_id" src:"json" desc:"角色ID"`
|
||||
Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"`
|
||||
|
||||
AppID string `json:"app_id" src:"json" desc:"应用ID"`
|
||||
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
|
||||
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
func (m *UserRole) AfterCreate(tx *gorm.DB) error {
|
||||
return tx.Model(&Role{}).Where("id = ?", m.RoleID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error
|
||||
}
|
||||
Loading…
Reference in New Issue