update to new vigo version

v3
veypi 4 months ago
parent 1880a6ce0f
commit a74ccb104f

@ -0,0 +1,21 @@
# 开发规范
## 注意
如果开发中发现什么开发规则或者技巧,你可以更新在这个文档,供其他人看
## UI界面开发指南
- 界面采用vyes.js 框架该框架可以将一个html文件自动加载为一个组件
- 开始写界面前请阅读全局样式文件 /ui/assets/common.css 保证所有界面的样式一致
- 组件内部避免重复的样式定义 如body内无需重复定义字体
- 本项目使用vyes-ui 组件库,该组件库可以通过 curl -sS http://localhost:4002/v/README.md 查看文档 其组件代码都已经映射到了/v/目录下
- 前端路由文件 /ui/routes.js 该文件定义了所有的路由规则
## vyes-ui 文档查看方法
curl 指令可以不用沙盒运行
获取文档目录 该操作可以查看所有组件的目录结构
curl -sS http://localhost:4000/v/docs/README.md?toc=1
获取文档全文(较长,一般建议查询目录再查询章节内容)
curl -sS http://localhost:4000/v/docs/README.md
获取章节内容(可以根据第一步获取的目录编号查询内容) 一般使用这个查询章节内容查询内容时不能带toc参数
curl -sS http://localhost:4000/v/docs/README.md?from=1.2&to=1.2

@ -13,18 +13,18 @@ import (
) )
type createOpts struct { type createOpts struct {
AppID string `json:"app_id" parse:"json"` AppID string `json:"app_id" src:"json" desc:"应用ID"`
UserID *string `json:"user_id" parse:"json"` UserID *string `json:"user_id" src:"json" desc:"用户ID"`
RoleID *string `json:"role_id" parse:"json"` RoleID *string `json:"role_id" src:"json" desc:"角色ID"`
ResourceID *string `json:"resource_id" parse:"json"` ResourceID *string `json:"resource_id" src:"json" desc:"资源ID"`
Name string `json:"name" parse:"json"` Name string `json:"name" src:"json" desc:"名称"`
TID string `json:"tid" parse:"json"` TID string `json:"tid" src:"json" desc:"资源ID"`
Level uint `json:"level" parse:"json"` Level uint `json:"level" src:"json" desc:"级别"`
} }
var _ = Router.Post("/", "创建访问权限", createAccess) var _ = Router.Post("/", "创建访问权限", createAccess)
func createAccess(x *vigo.X, opts *createOpts) (any, error) { func createAccess(x *vigo.X, opts *createOpts) (*models.Access, error) {
// 创建新记录 // 创建新记录
access := models.Access{ access := models.Access{
AppID: opts.AppID, AppID: opts.AppID,

@ -7,18 +7,22 @@
package access package access
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type deleteOpts struct { type deleteOpts struct {
ID string `parse:"path@id"` ID string `src:"path@id" desc:"记录ID"`
} }
var _ = Router.Delete("/{id}", "删除访问权限", deleteAccess) var _ = Router.Delete("/{id}", "删除访问权限", deleteAccess)
func deleteAccess(x *vigo.X, opts *deleteOpts) (any, error) { type deleteResponse struct {
Message string `json:"message"`
}
func deleteAccess(x *vigo.X, opts *deleteOpts) (*deleteResponse, error) {
// 查找记录 // 查找记录
var access models.Access var access models.Access
if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil { if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil {
@ -30,5 +34,5 @@ func deleteAccess(x *vigo.X, opts *deleteOpts) (any, error) {
return nil, err return nil, err
} }
return map[string]string{"message": "删除成功"}, nil return &deleteResponse{Message: "删除成功"}, nil
} }

@ -13,12 +13,12 @@ import (
) )
type getIDReq struct { type getIDReq struct {
ID string `parse:"path@id"` ID string `src:"path@id" desc:"记录ID"`
} }
var _ = Router.Get("/{id}", "获取访问权限详情", getAccess) var _ = Router.Get("/{id}", "获取访问权限详情", getAccess)
func getAccess(x *vigo.X, req *getIDReq) (any, error) { func getAccess(x *vigo.X, req *getIDReq) (*models.Access, error) {
// 查询数据库 // 查询数据库
var access models.Access var access models.Access
err := cfg.DB().Where("id = ?", req.ID).First(&access).Error err := cfg.DB().Where("id = ?", req.ID).First(&access).Error

@ -13,12 +13,12 @@ import (
) )
type listOpts struct { type listOpts struct {
Page int `parse:"query" default:"1"` Page int `src:"query" desc:"页码" default:"1"`
PageSize int `parse:"query" default:"20"` PageSize int `src:"query" desc:"每页大小" default:"20"`
AppID string `parse:"query"` AppID string `src:"query" desc:"应用ID"`
UserID string `parse:"query"` UserID string `src:"query" desc:"用户ID"`
RoleID string `parse:"query"` RoleID string `src:"query" desc:"角色ID"`
Name string `parse:"query"` Name string `src:"query" desc:"名称"`
} }
type listResponse struct { type listResponse struct {
@ -28,7 +28,7 @@ type listResponse struct {
var _ = Router.Get("/", "获取访问权限列表", listAccess) var _ = Router.Get("/", "获取访问权限列表", listAccess)
func listAccess(x *vigo.X, opts *listOpts) (any, error) { func listAccess(x *vigo.X, opts *listOpts) (*listResponse, error) {
// 构建查询 // 构建查询
db := cfg.DB().Model(&models.Access{}) db := cfg.DB().Model(&models.Access{})
if opts.AppID != "" { if opts.AppID != "" {

@ -13,15 +13,15 @@ import (
) )
type updateOpts struct { type updateOpts struct {
ID string `parse:"path@id"` ID string `src:"path@id" desc:"记录ID"`
Name *string `json:"name" parse:"json"` Name *string `json:"name" src:"json" desc:"名称"`
TID *string `json:"tid" parse:"json"` TID *string `json:"tid" src:"json" desc:"资源ID"`
Level *uint `json:"level" parse:"json"` Level *uint `json:"level" src:"json" desc:"级别"`
} }
var _ = Router.Patch("/{id}", "更新访问权限", updateAccess) var _ = Router.Patch("/{id}", "更新访问权限", updateAccess)
func updateAccess(x *vigo.X, opts *updateOpts) (any, error) { func updateAccess(x *vigo.X, opts *updateOpts) (*models.Access, error) {
// 查找记录 // 查找记录
var access models.Access var access models.Access
if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil { if err := cfg.DB().Where("id = ?", opts.ID).First(&access).Error; err != nil {

@ -14,12 +14,12 @@ import (
) )
type appIDReq struct { type appIDReq struct {
AppID string `parse:"path@app_id"` AppID string `src:"path@app_id" desc:"应用ID"`
} }
var _ = Router.Get("/{app_id}/key", "重置应用密钥", auth.Check("app", "app_id", auth.DoDelete), appKey) var _ = Router.Get("/{app_id}/key", "重置应用密钥", auth.Check("app", "app_id", auth.DoDelete), appKey)
func appKey(x *vigo.X, req *appIDReq) (any, error) { func appKey(x *vigo.X, req *appIDReq) (string, error) {
data := &models.App{} data := &models.App{}
data.ID = req.AppID data.ID = req.AppID
key := utils.RandSeq(32) key := utils.RandSeq(32)
@ -30,7 +30,7 @@ func appKey(x *vigo.X, req *appIDReq) (any, error) {
var _ = Router.Delete("/{app_id}", "删除应用", auth.Check("app", "app_id", auth.DoDelete), appDelete) var _ = Router.Delete("/{app_id}", "删除应用", auth.Check("app", "app_id", auth.DoDelete), appDelete)
func appDelete(x *vigo.X, req *appIDReq) (any, error) { func appDelete(x *vigo.X, req *appIDReq) (*models.App, error) {
data := &models.App{} data := &models.App{}
err := cfg.DB().Where("id = ?", req.AppID).Delete(data).Error err := cfg.DB().Where("id = ?", req.AppID).Delete(data).Error
@ -40,7 +40,7 @@ func appDelete(x *vigo.X, req *appIDReq) (any, error) {
var _ = Router.Get("/{app_id}", "获取应用详情", auth.Check("app", "app_id", auth.DoRead), appGet) var _ = Router.Get("/{app_id}", "获取应用详情", auth.Check("app", "app_id", auth.DoRead), appGet)
func appGet(x *vigo.X, req *appIDReq) (any, error) { func appGet(x *vigo.X, req *appIDReq) (*models.App, error) {
data := &models.App{} data := &models.App{}
err := cfg.DB().Where("id = ?", req.AppID).First(data).Error err := cfg.DB().Where("id = ?", req.AppID).First(data).Error
@ -48,19 +48,20 @@ func appGet(x *vigo.X, req *appIDReq) (any, error) {
} }
type listOpts struct { type listOpts struct {
Name *string `json:"name" parse:"query"` Name *string `json:"name" src:"query" desc:"应用名称"`
} }
var _ = Router.Get("/", "获取应用列表", appList) var _ = Router.Get("/", "获取应用列表", appList)
func appList(x *vigo.X, opts *listOpts) (any, error) { type appListItem struct {
data := make([]*struct { models.App
models.App UserStatus string `json:"user_status"`
UserStatus string `json:"user_status"` }
}, 0, 10)
tokenAny, err := auth.CheckJWT(x) func appList(x *vigo.X, opts *listOpts) ([]*appListItem, error) {
data := make([]*appListItem, 0, 10)
token, err := auth.CheckJWT(x)
if err == nil { if err == nil {
token := tokenAny.(*auth.Claims)
uid := token.UID uid := token.UID
query := cfg.DB().Table("apps").Select("apps.*,app_users.status user_status") query := cfg.DB().Table("apps").Select("apps.*,app_users.status user_status")
if opts.Name != nil { if opts.Name != nil {
@ -76,17 +77,17 @@ func appList(x *vigo.X, opts *listOpts) (any, error) {
} }
type postOpts struct { type postOpts struct {
Name string `json:"name" parse:"json"` Name string `json:"name" src:"json" desc:"应用名称"`
Icon string `json:"icon" parse:"json"` Icon string `json:"icon" src:"json" desc:"图标"`
Des *string `json:"des" parse:"json"` Des *string `json:"des" src:"json" desc:"描述"`
Typ string `json:"typ" parse:"json"` Typ string `json:"typ" src:"json" desc:"类型"`
Status string `json:"status" parse:"json"` Status string `json:"status" src:"json" desc:"状态"`
InitUrl string `json:"init_url" parse:"json"` InitUrl string `json:"init_url" src:"json" desc:"初始化URL"`
} }
var _ = Router.Post("/", "创建应用", auth.Check("app", "", auth.DoCreate), appPost) var _ = Router.Post("/", "创建应用", auth.Check("app", "", auth.DoCreate), appPost)
func appPost(x *vigo.X, opts *postOpts) (any, error) { func appPost(x *vigo.X, opts *postOpts) (*models.App, error) {
data := &models.App{} data := &models.App{}
data.Name = opts.Name data.Name = opts.Name
data.Icon = opts.Icon data.Icon = opts.Icon
@ -119,19 +120,19 @@ func appPost(x *vigo.X, opts *postOpts) (any, error) {
} }
type patchOpts struct { type patchOpts struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)" parse:"path@app_id"` ID string `json:"id" gorm:"primaryKey;type:varchar(36)" src:"path@app_id" desc:"应用ID"`
Name *string `json:"name" parse:"json"` Name *string `json:"name" src:"json" desc:"应用名称"`
Icon *string `json:"icon" parse:"json"` Icon *string `json:"icon" src:"json" desc:"图标"`
Des *string `json:"des" parse:"json"` Des *string `json:"des" src:"json" desc:"描述"`
Typ *string `json:"typ" gorm:"default:auto" parse:"json"` Typ *string `json:"typ" gorm:"default:auto" src:"json" desc:"类型"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36)" parse:"json"` InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36)" src:"json" desc:"初始角色ID"`
Status *string `json:"status" gorm:"default:ok" parse:"json"` Status *string `json:"status" gorm:"default:ok" src:"json" desc:"状态"`
InitUrl *string `json:"init_url" parse:"json"` InitUrl *string `json:"init_url" src:"json" desc:"初始化URL"`
} }
var _ = Router.Patch("/{app_id}", "更新应用", auth.Check("app", "app_id", auth.DoUpdate), appPatch) var _ = Router.Patch("/{app_id}", "更新应用", auth.Check("app", "app_id", auth.DoUpdate), appPatch)
func appPatch(x *vigo.X, opts *patchOpts) (any, error) { func appPatch(x *vigo.X, opts *patchOpts) (*models.App, error) {
data := &models.App{} data := &models.App{}
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error err := cfg.DB().Where("id = ?", opts.ID).First(data).Error

@ -7,14 +7,14 @@ import (
) )
type createOpts struct { type createOpts struct {
AppID string `parse:"path@app_id"` AppID string `src:"path@app_id" desc:"应用ID"`
UserID string `json:"user_id" parse:"json"` UserID string `json:"user_id" src:"json" desc:"用户ID"`
Status string `json:"status" parse:"json" default:"ok"` Status string `json:"status" src:"json" desc:"状态" default:"ok"`
} }
var _ = Router.Post("/", "创建应用用户", createAppUser) var _ = Router.Post("/", "创建应用用户", createAppUser)
func createAppUser(x *vigo.X, opts *createOpts) (any, error) { func createAppUser(x *vigo.X, opts *createOpts) (*models.AppUser, error) {
appUser := &models.AppUser{ appUser := &models.AppUser{
AppID: opts.AppID, AppID: opts.AppID,
UserID: opts.UserID, UserID: opts.UserID,

@ -7,13 +7,19 @@ import (
) )
type deleteOpts struct { type deleteOpts struct {
AppID string `parse:"path@app_id"` AppID string `src:"path@app_id" desc:"应用ID"`
UserID string `parse:"path@user_id"` UserID string `src:"path@user_id" desc:"用户ID"`
} }
var _ = Router.Delete("/{user_id}", "删除应用用户", deleteAppUser) var _ = Router.Delete("/{user_id}", "删除应用用户", deleteAppUser)
func deleteAppUser(x *vigo.X, opts *deleteOpts) (any, error) { 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{} appUser := &models.AppUser{}
if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil { 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) return nil, vigo.NewError("app_user not found").WithCode(404)
@ -23,9 +29,9 @@ func deleteAppUser(x *vigo.X, opts *deleteOpts) (any, error) {
return nil, err return nil, err
} }
return map[string]interface{}{ return &deleteAppUserResponse{
"message": "app_user deleted successfully", Message: "app_user deleted successfully",
"app_id": opts.AppID, AppID: opts.AppID,
"user_id": opts.UserID, UserID: opts.UserID,
}, nil }, nil
} }

@ -7,13 +7,13 @@ import (
) )
type appUserIDReq struct { type appUserIDReq struct {
AppID string `parse:"path@app_id"` AppID string `src:"path@app_id" desc:"应用ID"`
UserID string `parse:"path@user_id"` UserID string `src:"path@user_id" desc:"用户ID"`
} }
var _ = Router.Get("/{user_id}", "获取应用用户详情", getAppUser) var _ = Router.Get("/{user_id}", "获取应用用户详情", getAppUser)
func getAppUser(x *vigo.X, req *appUserIDReq) (any, error) { func getAppUser(x *vigo.X, req *appUserIDReq) (*models.AppUser, error) {
appUser := &models.AppUser{} appUser := &models.AppUser{}
err := cfg.DB().Where("app_id = ? AND user_id = ?", req.AppID, req.UserID).First(appUser).Error err := cfg.DB().Where("app_id = ? AND user_id = ?", req.AppID, req.UserID).First(appUser).Error
if err != nil { if err != nil {

@ -1,16 +1,16 @@
package app_user package app_user
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type listOpts struct { type listOpts struct {
AppID string `parse:"path@app_id"` AppID string `src:"path@app_id" desc:"应用ID"`
Page int `parse:"query" default:"1"` Page int `src:"query" desc:"页码" default:"1"`
PageSize int `parse:"query" default:"20"` PageSize int `src:"query" desc:"每页数量" default:"20"`
Status string `parse:"query" default:""` Status string `src:"query" desc:"状态" default:""`
} }
type listResponse struct { type listResponse struct {
@ -20,7 +20,7 @@ type listResponse struct {
var _ = Router.Get("/", "获取应用用户列表", listAppUsers) var _ = Router.Get("/", "获取应用用户列表", listAppUsers)
func listAppUsers(x *vigo.X, opts *listOpts) (any, error) { func listAppUsers(x *vigo.X, opts *listOpts) (*listResponse, error) {
query := cfg.DB().Model(&models.AppUser{}).Where("app_id = ?", opts.AppID) query := cfg.DB().Model(&models.AppUser{}).Where("app_id = ?", opts.AppID)
if opts.Status != "" { if opts.Status != "" {

@ -1,20 +1,20 @@
package app_user package app_user
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type updateOpts struct { type updateOpts struct {
AppID string `parse:"path@app_id"` AppID string `src:"path@app_id" desc:"应用ID"`
UserID string `parse:"path@user_id"` UserID string `src:"path@user_id" desc:"用户ID"`
Status *string `json:"status" parse:"json"` Status *string `json:"status" src:"json" desc:"状态"`
} }
var _ = Router.Patch("/{user_id}", "更新应用用户", updateAppUser) var _ = Router.Patch("/{user_id}", "更新应用用户", updateAppUser)
func updateAppUser(x *vigo.X, opts *updateOpts) (any, error) { func updateAppUser(x *vigo.X, opts *updateOpts) (*models.AppUser, error) {
appUser := &models.AppUser{} appUser := &models.AppUser{}
if err := cfg.DB().Where("app_id = ? AND user_id = ?", opts.AppID, opts.UserID).First(appUser).Error; err != nil { 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) return nil, vigo.NewError("app_user not found").WithCode(404)

@ -1,20 +1,20 @@
package resource package resource
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type createOpts struct { type createOpts struct {
AppID string `json:"app_id" parse:"path@app_id"` AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"`
Name string `json:"name" parse:"json"` Name string `json:"name" src:"json" desc:"资源名称"`
Des string `json:"des" parse:"json"` Des string `json:"des" src:"json" desc:"资源描述"`
} }
var _ = Router.Post("/", "创建资源", createResource) var _ = Router.Post("/", "创建资源", createResource)
func createResource(x *vigo.X, opts *createOpts) (any, error) { func createResource(x *vigo.X, opts *createOpts) (*models.Resource, error) {
// 创建资源对象 // 创建资源对象
resource := &models.Resource{ resource := &models.Resource{
AppID: opts.AppID, AppID: opts.AppID,

@ -1,18 +1,23 @@
package resource package resource
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type deleteOpts struct { type deleteOpts struct {
ResourceID string `parse:"path@resource_id"` ResourceID string `src:"path@resource_id" desc:"资源ID"`
} }
var _ = Router.Delete("/{resource_id}", "删除资源", deleteResource) var _ = Router.Delete("/{resource_id}", "删除资源", deleteResource)
func deleteResource(x *vigo.X, opts *deleteOpts) (any, error) { type deleteResourceResponse struct {
Message string `json:"message"`
ID string `json:"id"`
}
func deleteResource(x *vigo.X, opts *deleteOpts) (*deleteResourceResponse, error) {
// 查找资源 // 查找资源
resource := &models.Resource{} resource := &models.Resource{}
if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil { if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil {
@ -24,8 +29,8 @@ func deleteResource(x *vigo.X, opts *deleteOpts) (any, error) {
return nil, vigo.NewError("failed to delete resource").WithCode(500) return nil, vigo.NewError("failed to delete resource").WithCode(500)
} }
return map[string]interface{}{ return &deleteResourceResponse{
"message": "resource deleted successfully", Message: "resource deleted successfully",
"id": opts.ResourceID, ID: opts.ResourceID,
}, nil }, nil
} }

@ -1,18 +1,18 @@
package resource package resource
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type getIDReq struct { type getIDReq struct {
ID string `parse:"path@resource_id"` ID string `src:"path@resource_id" desc:"资源ID"`
} }
var _ = Router.Get("/{resource_id}", "获取资源详情", getResource) var _ = Router.Get("/{resource_id}", "获取资源详情", getResource)
func getResource(x *vigo.X, req *getIDReq) (any, error) { func getResource(x *vigo.X, req *getIDReq) (*models.Resource, error) {
// 查询数据库 // 查询数据库
resource := &models.Resource{} resource := &models.Resource{}
err := cfg.DB().Where("id = ?", req.ID).First(resource).Error err := cfg.DB().Where("id = ?", req.ID).First(resource).Error

@ -1,16 +1,16 @@
package resource package resource
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type listOpts struct { type listOpts struct {
Page int `parse:"query" default:"1"` Page int `src:"query" desc:"页码" default:"1"`
PageSize int `parse:"query" default:"20"` PageSize int `src:"query" desc:"每页数量" default:"20"`
AppID string `parse:"query"` AppID string `src:"query" desc:"应用ID"`
Name string `parse:"query"` Name string `src:"query" desc:"资源名称"`
} }
type listResponse struct { type listResponse struct {
@ -20,7 +20,7 @@ type listResponse struct {
var _ = Router.Get("/", "获取资源列表", listResources) var _ = Router.Get("/", "获取资源列表", listResources)
func listResources(x *vigo.X, opts *listOpts) (any, error) { func listResources(x *vigo.X, opts *listOpts) (*listResponse, error) {
// 构建查询 // 构建查询
db := cfg.DB().Model(&models.Resource{}) db := cfg.DB().Model(&models.Resource{})
if opts.AppID != "" { if opts.AppID != "" {

@ -1,20 +1,20 @@
package resource package resource
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type updateOpts struct { type updateOpts struct {
ResourceID string `parse:"path@resource_id"` ResourceID string `src:"path@resource_id" desc:"资源ID"`
Name *string `json:"name" parse:"json"` Name *string `json:"name" src:"json" desc:"资源名称"`
Des *string `json:"des" parse:"json"` Des *string `json:"des" src:"json" desc:"资源描述"`
} }
var _ = Router.Patch("/{resource_id}", "更新资源", updateResource) var _ = Router.Patch("/{resource_id}", "更新资源", updateResource)
func updateResource(x *vigo.X, opts *updateOpts) (any, error) { func updateResource(x *vigo.X, opts *updateOpts) (*models.Resource, error) {
// 查找资源 // 查找资源
resource := &models.Resource{} resource := &models.Resource{}
if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil { if err := cfg.DB().Where("id = ?", opts.ResourceID).First(resource).Error; err != nil {

@ -7,14 +7,14 @@ import (
) )
type createOpts struct { type createOpts struct {
AppID string `parse:"path@app_id"` // 应用 ID AppID string `src:"path@app_id" desc:"应用ID"` // 应用 ID
Name string `json:"name" parse:"json"` // 角色名称 Name string `json:"name" src:"json" desc:"角色名称"` // 角色名称
Des string `json:"des" parse:"json"` // 角色描述 Des string `json:"des" src:"json" desc:"角色描述"` // 角色描述
} }
var _ = Router.Post("/", "创建角色", createRole) var _ = Router.Post("/", "创建角色", createRole)
func createRole(x *vigo.X, opts *createOpts) (any, error) { func createRole(x *vigo.X, opts *createOpts) (*models.Role, error) {
// 创建角色 // 创建角色
role := &models.Role{ role := &models.Role{
AppID: opts.AppID, AppID: opts.AppID,

@ -1,18 +1,23 @@
package role package role
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type deleteOpts struct { type deleteOpts struct {
RoleID string `parse:"path@role_id"` // 角色 ID RoleID string `src:"path@role_id" desc:"角色ID"` // 角色 ID
} }
var _ = Router.Delete("/{role_id}", "删除角色", deleteRole) var _ = Router.Delete("/{role_id}", "删除角色", deleteRole)
func deleteRole(x *vigo.X, opts *deleteOpts) (any, error) { type deleteRoleResponse struct {
Message string `json:"message"`
ID string `json:"id"`
}
func deleteRole(x *vigo.X, opts *deleteOpts) (*deleteRoleResponse, error) {
// 查找角色 // 查找角色
role := &models.Role{} role := &models.Role{}
if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil { if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil {
@ -25,8 +30,8 @@ func deleteRole(x *vigo.X, opts *deleteOpts) (any, error) {
} }
// 返回成功消息 // 返回成功消息
return map[string]interface{}{ return &deleteRoleResponse{
"message": "role deleted successfully", Message: "role deleted successfully",
"id": opts.RoleID, ID: opts.RoleID,
}, nil }, nil
} }

@ -1,18 +1,18 @@
package role package role
import ( import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models" "github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
) )
type getIDReq struct { type getIDReq struct {
ID string `parse:"path@role_id"` ID string `src:"path@role_id" desc:"角色ID"`
} }
var _ = Router.Get("/{role_id}", "获取角色详情", getRole) var _ = Router.Get("/{role_id}", "获取角色详情", getRole)
func getRole(x *vigo.X, req *getIDReq) (any, error) { func getRole(x *vigo.X, req *getIDReq) (*models.Role, error) {
// 查询数据库 // 查询数据库
role := &models.Role{} role := &models.Role{}
err := cfg.DB().Where("id = ?", req.ID).First(role).Error err := cfg.DB().Where("id = ?", req.ID).First(role).Error

@ -9,12 +9,12 @@ import (
var _ = Router.Get("/", "获取角色列表", listRoles) var _ = Router.Get("/", "获取角色列表", listRoles)
type listOpts struct { type listOpts struct {
AppID string `parse:"path@app_id"` AppID string `src:"path@app_id" desc:"应用ID"`
Page int `parse:"query" default:"1"` Page int `src:"query" default:"1" desc:"页码"`
PageSize int `parse:"query" default:"20"` PageSize int `src:"query" default:"20" desc:"页大小"`
Keyword string `parse:"query" default:""` Keyword string `src:"query" default:"" desc:"关键词"`
SortBy string `parse:"query" default:"created_at"` SortBy string `src:"query" default:"created_at" desc:"排序字段"`
Order string `parse:"query" default:"desc"` Order string `src:"query" default:"desc" desc:"排序方向"`
} }
type listResponse struct { type listResponse struct {
@ -22,7 +22,7 @@ type listResponse struct {
Items []*models.Role `json:"items"` Items []*models.Role `json:"items"`
} }
func listRoles(x *vigo.X, opts *listOpts) (any, error) { func listRoles(x *vigo.X, opts *listOpts) (*listResponse, error) {
// 构建查询 // 构建查询
db := cfg.DB().Model(&models.Role{}).Where("app_id = ?", opts.AppID) db := cfg.DB().Model(&models.Role{}).Where("app_id = ?", opts.AppID)
query := db query := db

@ -7,14 +7,14 @@ import (
) )
type updateOpts struct { type updateOpts struct {
RoleID string `parse:"path@role_id"` // 角色 ID RoleID string `src:"path@role_id" desc:"角色ID"` // 角色 ID
Name *string `json:"name" parse:"json"` // 可选,角色名称 Name *string `json:"name" src:"json" desc:"角色名称"` // 可选,角色名称
Des *string `json:"des" parse:"json"` // 可选,角色描述 Des *string `json:"des" src:"json" desc:"角色描述"` // 可选,角色描述
} }
var _ = Router.Patch("/{role_id}", "更新角色", updateRole) var _ = Router.Patch("/{role_id}", "更新角色", updateRole)
func updateRole(x *vigo.X, opts *updateOpts) (any, error) { func updateRole(x *vigo.X, opts *updateOpts) (*models.Role, error) {
// 查找角色 // 查找角色
role := &models.Role{} role := &models.Role{}
if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil { if err := cfg.DB().Where("id = ?", opts.RoleID).First(role).Error; err != nil {

@ -14,7 +14,6 @@ import (
"github.com/veypi/OneAuth/api/user" "github.com/veypi/OneAuth/api/user"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/libs/auth" "github.com/veypi/OneAuth/libs/auth"
"github.com/veypi/OneAuth/oauth"
"github.com/vyes-ai/vigo" "github.com/vyes-ai/vigo"
"github.com/vyes-ai/vigo/contrib/common" "github.com/vyes-ai/vigo/contrib/common"
) )
@ -23,7 +22,7 @@ var Router = vigo.NewRouter()
func init() { func init() {
// 注册全局中间件 // 注册全局中间件
Router.Use(auth.CheckJWT) Router.Use(func(x *vigo.X) (any, error) { return auth.CheckJWT(x) })
Router.After(common.JsonResponse, common.JsonErrorResponse) Router.After(common.JsonResponse, common.JsonErrorResponse)
// 注册子资源路由 // 注册子资源路由
@ -31,19 +30,23 @@ func init() {
Router.Extend("token", token.Router) Router.Extend("token", token.Router)
Router.Extend("app", app.Router) Router.Extend("app", app.Router)
Router.Extend("sms", sms.Router) Router.Extend("sms", sms.Router)
Router.Extend("oauth", oauth.Router) // Router.Extend("oauth", oauth.Router)
// 注册基础接口 // 注册基础接口
Router.Get("/cfg", "获取配置信息", vigo.SkipBefore, getCfg) Router.Get("/cfg", "获取配置信息", vigo.SkipBefore, getCfg)
// 404 处理 // 404 处理
Router.Any("/**", vigo.SkipBefore, func(x *vigo.X) error { Router.Any("/**", vigo.SkipBefore, "拦截未注册的api请求返回404", func(x *vigo.X) error {
return vigo.ErrNotFound return vigo.ErrNotFound
}) })
} }
func getCfg(x *vigo.X) any { type CfgResponse struct {
return map[string]any{ SMS bool `json:"sms" desc:"是否启用短信服务"`
"sms": cfg.Config.SMS.Enable, }
func getCfg(x *vigo.X) *CfgResponse {
return &CfgResponse{
SMS: cfg.Config.SMS.Enable,
} }
} }

@ -15,10 +15,10 @@ import (
// SendCodeRequest 发送验证码请求 // SendCodeRequest 发送验证码请求
type SendCodeRequest struct { type SendCodeRequest struct {
Phone string `json:"phone" parse:"json"` Phone string `json:"phone" src:"json" desc:"手机号"`
Region string `json:"region" parse:"json"` Region string `json:"region" src:"json" desc:"区域"`
Purpose string `json:"purpose" parse:"json"` Purpose string `json:"purpose" src:"json" desc:"用途"`
TemplateID string `json:"template_id" parse:"json"` TemplateID string `json:"template_id" src:"json" desc:"模板ID"`
} }
// SendCodeResponse 发送验证码响应 // SendCodeResponse 发送验证码响应
@ -29,10 +29,10 @@ type SendCodeResponse struct {
Interval int `json:"interval"` // 下次可发送间隔(秒) Interval int `json:"interval"` // 下次可发送间隔(秒)
} }
var _ = Router.Post("/", "发送验证码", vigo.SkipBefore, limiter.NewAdvancedRequestLimiter(time.Minute*3, 5, time.Second*30), sendCode) var _ = Router.Post("/", "发送验证码", vigo.SkipBefore, limiter.NewAdvancedRequestLimiter(time.Minute*3, 5, time.Second*30).Limit, sendCode)
// SendCode 发送验证码 // SendCode 发送验证码
func sendCode(x *vigo.X, req *SendCodeRequest) (any, error) { func sendCode(x *vigo.X, req *SendCodeRequest) (*SendCodeResponse, error) {
// 1. 验证手机号 // 1. 验证手机号
normalizedPhone := utils.NormalizePhoneNumber(req.Phone) normalizedPhone := utils.NormalizePhoneNumber(req.Phone)
if !utils.ValidatePhoneNumber(normalizedPhone) { if !utils.ValidatePhoneNumber(normalizedPhone) {

@ -9,14 +9,14 @@ import (
) )
type patchOpts struct { type patchOpts struct {
ID string `json:"id" parse:"path@token_id"` ID string `json:"id" src:"path@token_id" desc:"令牌ID"`
ExpiredAt *time.Time `json:"expired_at" parse:"json"` ExpiredAt *time.Time `json:"expired_at" src:"json" desc:"过期时间"`
OverPerm *string `json:"over_perm" parse:"json"` OverPerm *string `json:"over_perm" src:"json" desc:"覆盖权限"`
} }
var _ = Router.Patch("/{token_id}", "更新 Token", tokenPatch) var _ = Router.Patch("/{token_id}", "更新 Token", tokenPatch)
func tokenPatch(x *vigo.X, opts *patchOpts) (any, error) { func tokenPatch(x *vigo.X, opts *patchOpts) (*models.Token, error) {
data := &models.Token{} data := &models.Token{}
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
@ -36,12 +36,12 @@ func tokenPatch(x *vigo.X, opts *patchOpts) (any, error) {
} }
type deleteOpts struct { type deleteOpts struct {
ID string `parse:"path@token_id"` ID string `src:"path@token_id" desc:"令牌ID"`
} }
var _ = Router.Delete("/{token_id}", "删除 Token", tokenDelete) var _ = Router.Delete("/{token_id}", "删除 Token", tokenDelete)
func tokenDelete(x *vigo.X, opts *deleteOpts) (any, error) { func tokenDelete(x *vigo.X, opts *deleteOpts) (*models.Token, error) {
data := &models.Token{} data := &models.Token{}
err := cfg.DB().Where("id = ?", opts.ID).Delete(data).Error err := cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
return data, err return data, err

@ -21,19 +21,19 @@ import (
type postOpts struct { type postOpts struct {
// 两种获取token方式一种用refreshtoken换取apptoken(应用登录)一种用密码加密code换refreshtoken (oa登录) // 两种获取token方式一种用refreshtoken换取apptoken(应用登录)一种用密码加密code换refreshtoken (oa登录)
Refresh *string `json:"refresh" parse:"json"` Refresh *string `json:"refresh" src:"json" desc:"刷新令牌"`
Typ *string `json:"typ" parse:"json"` Typ *string `json:"typ" src:"json" desc:"令牌类型"`
AppID *string `json:"app_id" gorm:"index;type:varchar(36)" parse:"json"` AppID *string `json:"app_id" gorm:"index;type:varchar(36)" src:"json" desc:"应用ID"`
ExpiredAt *time.Time `json:"expired_at" parse:"json"` ExpiredAt *time.Time `json:"expired_at" src:"json" desc:"过期时间"`
OverPerm *string `json:"over_perm" parse:"json"` OverPerm *string `json:"over_perm" src:"json" desc:"覆盖权限"`
Device *string `json:"device" parse:"json"` Device *string `json:"device" src:"json" desc:"设备信息"`
} }
var _ = Router.Post("/", "创建 Token", vigo.SkipBefore, tokenPost) var _ = Router.Post("/", "创建 Token", vigo.SkipBefore, tokenPost)
// for user login app // for user login app
func tokenPost(x *vigo.X, opts *postOpts) (any, error) { func tokenPost(x *vigo.X, opts *postOpts) (string, error) {
aid := cfg.Config.ID aid := cfg.Config.ID
if opts.AppID != nil && *opts.AppID != "" { if opts.AppID != nil && *opts.AppID != "" {
aid = *opts.AppID aid = *opts.AppID
@ -50,14 +50,14 @@ func tokenPost(x *vigo.X, opts *postOpts) (any, error) {
// for other app redirect // for other app redirect
refresh, err := auth.ParseJwt(*opts.Refresh) refresh, err := auth.ParseJwt(*opts.Refresh)
if err != nil { if err != nil {
return nil, err return "", err
} }
if refresh.ID == "" { if refresh.ID == "" {
return nil, vigo.ErrNotAuthorized return "", vigo.ErrNotAuthorized
} }
err = cfg.DB().Where("id = ?", refresh.ID).First(data).Error err = cfg.DB().Where("id = ?", refresh.ID).First(data).Error
if err != nil { if err != nil {
return nil, err return "", err
} }
claim.AID = aid claim.AID = aid
claim.UID = refresh.UID claim.UID = refresh.UID
@ -79,7 +79,7 @@ func tokenPost(x *vigo.X, opts *postOpts) (any, error) {
app := &models.App{} app := &models.App{}
err = cfg.DB().Where("id = ?", aid).First(app).Error err = cfg.DB().Where("id = ?", aid).First(app).Error
if err != nil { if err != nil {
return nil, err return "", err
} }
return auth.GenJwtWithKey(claim, app.Key) return auth.GenJwtWithKey(claim, app.Key)
} else if refresh.ID == cfg.Config.ID { } else if refresh.ID == cfg.Config.ID {
@ -101,7 +101,7 @@ func tokenPost(x *vigo.X, opts *postOpts) (any, error) {
claim.ExpiresAt = jwt.NewNumericDate(newToken.ExpiredAt) claim.ExpiresAt = jwt.NewNumericDate(newToken.ExpiredAt)
return auth.GenJwt(claim) return auth.GenJwt(claim)
} else { } else {
return nil, vigo.ErrNotPermitted return "", vigo.ErrNotPermitted
} }
} else if typ == "ufs" { } else if typ == "ufs" {
claim.AID = refresh.AID claim.AID = refresh.AID
@ -125,9 +125,9 @@ func tokenPost(x *vigo.X, opts *postOpts) (any, error) {
http.SetCookie(x.ResponseWriter(), cookie) http.SetCookie(x.ResponseWriter(), cookie)
return token, nil return token, nil
} else { } else {
return nil, vigo.ErrArgInvalid return "", vigo.ErrArgInvalid
} }
} else { } else {
return nil, vigo.ErrArgInvalid return "", vigo.ErrArgInvalid
} }
} }

@ -14,26 +14,26 @@ import (
) )
type getOpts struct { type getOpts struct {
ID string `json:"id" parse:"path@token_id"` ID string `json:"id" src:"path@token_id" desc:"令牌ID"`
} }
var _ = Router.Get("/{token_id}", "获取 Token 详情", tokenGet) var _ = Router.Get("/{token_id}", "获取 Token 详情", tokenGet)
func tokenGet(x *vigo.X, opts *getOpts) (any, error) { func tokenGet(x *vigo.X, opts *getOpts) (*models.Token, error) {
data := &models.Token{} data := &models.Token{}
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
return data, err return data, err
} }
type listOpts struct { type listOpts struct {
Limit int `json:"limit"` Limit int `json:"limit" src:"query" desc:"限制数量"`
UserID string `json:"user_id" gorm:"index;type:varchar(36)" parse:"query"` UserID string `json:"user_id" gorm:"index;type:varchar(36)" src:"query" desc:"用户ID"`
AppID string `json:"app_id" gorm:"index;type:varchar(36)" parse:"query"` AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"query" desc:"应用ID"`
} }
var _ = Router.Get("/", "获取 Token 列表", tokenList) var _ = Router.Get("/", "获取 Token 列表", tokenList)
func tokenList(x *vigo.X, opts *listOpts) (any, error) { func tokenList(x *vigo.X, opts *listOpts) ([]*models.Token, error) {
data := make([]*models.Token, 0, 10) data := make([]*models.Token, 0, 10)
query := cfg.DB() query := cfg.DB()

@ -26,18 +26,18 @@ import (
var _ = Router.Post("/", "用户注册", vigo.SkipBefore, publicLimits, userPost) var _ = Router.Post("/", "用户注册", vigo.SkipBefore, publicLimits, userPost)
type postOpts struct { type postOpts struct {
Username string `json:"username" gorm:"varchar(100);unique;default:not null" parse:"json"` Username string `json:"username" gorm:"varchar(100);unique;default:not null" src:"json" desc:"用户名"`
Code string `json:"code" gorm:"varchar(128)" parse:"json"` Code string `json:"code" gorm:"varchar(128)" src:"json" desc:"授权码/密码"`
Phone string `json:"phone" gorm:"varchar(50);unique;default:null" parse:"json"` Phone string `json:"phone" gorm:"varchar(50);unique;default:null" src:"json" desc:"手机号"`
VerifyCode string `json:"verify_code" parse:"json"` VerifyCode string `json:"verify_code" src:"json" desc:"验证码"`
Region string `json:"region" parse:"json"` Region string `json:"region" src:"json" desc:"区域"`
Nickname *string `json:"nickname" parse:"json"` Nickname *string `json:"nickname" src:"json" desc:"昵称"`
Icon *string `json:"icon" parse:"json"` Icon *string `json:"icon" src:"json" desc:"头像"`
Email *string `json:"email" gorm:"varchar(20);unique;default:null" parse:"json"` Email *string `json:"email" gorm:"varchar(20);unique;default:null" src:"json" desc:"邮箱"`
} }
func userPost(x *vigo.X, opts *postOpts) (any, error) { func userPost(x *vigo.X, opts *postOpts) (*models.User, error) {
data := &models.User{} data := &models.User{}
if cfg.Config.SMS.Enable { if cfg.Config.SMS.Enable {

@ -29,20 +29,20 @@ var _ = Router.Post("/login", "用户登录",
userLogin) userLogin)
type loginOpts struct { type loginOpts struct {
UserName string `json:"username" parse:"json"` UserName string `json:"username" src:"json" desc:"用户名"`
Code string `json:"code" parse:"json"` Code string `json:"code" src:"json" desc:"密码/授权码"`
VerifyCode string `json:"verify_code" parse:"json"` VerifyCode string `json:"verify_code" src:"json" desc:"验证码"`
Region string `json:"region" parse:"json"` Region string `json:"region" src:"json" desc:"区域"`
Phone string `json:"phone" parse:"json"` Phone string `json:"phone" src:"json" desc:"手机号"`
Email string `json:"email" parse:"json"` Email string `json:"email" src:"json" desc:"邮箱"`
Type *string `json:"type" parse:"json"` Type *string `json:"type" src:"json" desc:"登录类型"`
AppID *string `json:"app_id" parse:"json"` AppID *string `json:"app_id" src:"json" desc:"应用ID"`
Device *string `json:"device" parse:"json"` Device *string `json:"device" src:"json" desc:"设备信息"`
} }
func userLogin(x *vigo.X, opts *loginOpts) (any, error) { func userLogin(x *vigo.X, opts *loginOpts) (string, error) {
// Implement login logic here // Implement login logic here
// For example, validate user credentials and return a token // For example, validate user credentials and return a token
user := &models.User{} user := &models.User{}
@ -61,24 +61,24 @@ func userLogin(x *vigo.X, opts *loginOpts) (any, error) {
} }
err := query.First(user).Error err := query.First(user).Error
if err != nil { if err != nil {
return nil, vigo.ErrNotFound return "", vigo.ErrNotFound
} }
logv.Info().Str("user", user.ID).Msg("login") logv.Info().Str("user", user.ID).Msg("login")
if opts.VerifyCode != "" { if opts.VerifyCode != "" {
err = sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signin") err = sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signin")
if err != nil { if err != nil {
logv.Warn().Msgf("verify code: %v", err) logv.Warn().Msgf("verify code: %v", err)
return nil, vigo.ErrNotAuthorized.WithError(err) return "", vigo.ErrNotAuthorized.WithError(err)
} }
} else { } else {
code, err := base64.URLEncoding.DecodeString(opts.Code) code, err := base64.URLEncoding.DecodeString(opts.Code)
if err != nil { if err != nil {
return nil, vigo.ErrArgInvalid.WithArgs("code") return "", vigo.ErrArgInvalid.WithArgs("code")
} }
ncode, err := utils.AesDecrypt([]byte(user.Code), utils.PKCS7Padding(code, 32), []byte(user.Salt)) ncode, err := utils.AesDecrypt([]byte(user.Code), utils.PKCS7Padding(code, 32), []byte(user.Salt))
if err != nil || string(ncode) != user.ID { if err != nil || string(ncode) != user.ID {
return nil, vigo.ErrNotAuthorized return "", vigo.ErrNotAuthorized
} }
} }
aid := cfg.Config.ID aid := cfg.Config.ID
@ -97,22 +97,18 @@ func userLogin(x *vigo.X, opts *loginOpts) (any, error) {
err = cfg.DB().Create(data).Error err = cfg.DB().Create(data).Error
if err != nil { if err != nil {
return nil, err return "", err
} }
claim := &auth.Claims{} claim := &auth.Claims{}
claim.AID = aid
claim.UID = user.ID
claim.ID = data.ID claim.ID = data.ID
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt) claim.UID = user.ID
claim.AID = aid
claim.Name = user.Username claim.Name = user.Username
if user.Nickname != "" {
claim.Name = user.Nickname
}
claim.Icon = user.Icon claim.Icon = user.Icon
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
claim.IssuedAt = jwt.NewNumericDate(time.Now())
claim.Issuer = cfg.Config.ID
token, err := auth.GenJwt(claim) return auth.GenJwt(claim)
return map[string]any{
"token": token,
"expired_at": data.ExpiredAt,
}, err
} }

@ -16,13 +16,13 @@ import (
var _ = Router.Post("/", "创建用户角色", userRolePost) var _ = Router.Post("/", "创建用户角色", userRolePost)
type postOpts struct { type postOpts struct {
UserID string `json:"user_id" parse:"path"` UserID string `json:"user_id" src:"path" desc:"用户ID"`
RoleID string `json:"role_id" parse:"json"` RoleID string `json:"role_id" src:"json" desc:"角色ID"`
AppID string `json:"app_id" parse:"json"` AppID string `json:"app_id" src:"json" desc:"应用ID"`
Status string `json:"status" parse:"json"` Status string `json:"status" src:"json" desc:"状态"`
} }
func userRolePost(x *vigo.X, opts *postOpts) (any, error) { func userRolePost(x *vigo.X, opts *postOpts) (*models.UserRole, error) {
data := &models.UserRole{} data := &models.UserRole{}
data.UserID = opts.UserID data.UserID = opts.UserID

@ -14,12 +14,12 @@ import (
) )
type deleteIDReq struct { type deleteIDReq struct {
ID string `parse:"path@id"` ID string `src:"path@id" desc:"记录ID"`
} }
var _ = Router.Delete("/{id}", "删除用户角色", userRoleDelete) var _ = Router.Delete("/{id}", "删除用户角色", userRoleDelete)
func userRoleDelete(x *vigo.X, req *deleteIDReq) (any, error) { func userRoleDelete(x *vigo.X, req *deleteIDReq) (*models.UserRole, error) {
data := &models.UserRole{} data := &models.UserRole{}
err := cfg.DB().Where("id = ?", req.ID).Delete(data).Error err := cfg.DB().Where("id = ?", req.ID).Delete(data).Error
return data, err return data, err

@ -15,34 +15,36 @@ import (
) )
type getIDReq struct { type getIDReq struct {
ID string `parse:"path@id"` ID string `src:"path@id" desc:"记录ID"`
} }
var _ = Router.Get("/{id}", "获取用户角色详情", userRoleGet) var _ = Router.Get("/{id}", "获取用户角色详情", userRoleGet)
func userRoleGet(x *vigo.X, req *getIDReq) (any, error) { func userRoleGet(x *vigo.X, req *getIDReq) (*models.UserRole, error) {
data := &models.UserRole{} data := &models.UserRole{}
err := cfg.DB().Where("id = ?", req.ID).First(data).Error err := cfg.DB().Where("id = ?", req.ID).First(data).Error
return data, err return data, err
} }
type listOpts struct { type listOpts struct {
UserID *string `json:"user_id" parse:"path@user_id"` UserID *string `json:"user_id" src:"path@user_id" desc:"用户ID"`
RoleID *string `json:"role_id" parse:"query"` RoleID *string `json:"role_id" src:"query" desc:"角色ID"`
AppID *string `json:"app_id" parse:"query"` AppID *string `json:"app_id" src:"query" desc:"应用ID"`
Status *string `json:"status" parse:"query"` Status *string `json:"status" src:"query" desc:"状态"`
} }
var _ = Router.Get("/", "获取用户角色列表", userRoleList) var _ = Router.Get("/", "获取用户角色列表", userRoleList)
func userRoleList(x *vigo.X, opts *listOpts) (any, error) { type userRoleListItem struct {
data := make([]*struct { models.UserRole
models.UserRole Username string `json:"username"`
Username string `json:"username"` Nickname string `json:"nickname"`
Nickname string `json:"nickname"` Icon string `json:"icon"`
Icon string `json:"icon"` RoleName string `json:"role_name"`
RoleName string `json:"role_name"` }
}, 0, 10)
func userRoleList(x *vigo.X, opts *listOpts) ([]*userRoleListItem, error) {
data := make([]*userRoleListItem, 0, 10)
// data := make([]*M.UserRole, 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"). query := cfg.DB().Debug().Table("user_roles").Select("user_roles.*,users.username,users.nickname,users.icon,roles.name as role_name").

@ -14,13 +14,13 @@ import (
) )
type patchOpts struct { type patchOpts struct {
ID string `json:"id" parse:"path@id"` ID string `json:"id" src:"path@id" desc:"记录ID"`
Status *string `json:"status" parse:"json"` Status *string `json:"status" src:"json" desc:"状态"`
} }
var _ = Router.Patch("/{id}", "更新用户角色", userRolePatch) var _ = Router.Patch("/{id}", "更新用户角色", userRolePatch)
func userRolePatch(x *vigo.X, opts *patchOpts) (any, error) { func userRolePatch(x *vigo.X, opts *patchOpts) (*models.UserRole, error) {
data := &models.UserRole{} data := &models.UserRole{}
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
if err != nil { if err != nil {

@ -9,12 +9,12 @@ import (
) )
type userIDReq struct { type userIDReq struct {
UserID string `parse:"path@user_id"` UserID string `src:"path@user_id" desc:"用户ID"`
} }
var _ = Router.Delete("/{user_id}", "删除用户", auth.Check("user", "user_id", auth.DoDelete, checkOwner), userDelete) var _ = Router.Delete("/{user_id}", "删除用户", auth.Check("user", "user_id", auth.DoDelete, checkOwner), userDelete)
func userDelete(x *vigo.X, req *userIDReq) (any, error) { func userDelete(x *vigo.X, req *userIDReq) (*models.User, error) {
data := &models.User{} data := &models.User{}
err := cfg.DB().Where("id = ?", req.UserID).Delete(data).Error err := cfg.DB().Where("id = ?", req.UserID).Delete(data).Error
return data, err return data, err
@ -27,7 +27,7 @@ func checkOwner(x *vigo.X, data any) bool {
return ok1 && u.ID == x.PathParams.Get("user_id") return ok1 && u.ID == x.PathParams.Get("user_id")
} }
func userGet(x *vigo.X, req *userIDReq) (any, error) { func userGet(x *vigo.X, req *userIDReq) (*models.User, error) {
data := &models.User{} data := &models.User{}
err := cfg.DB().Where("id = ?", req.UserID).First(data).Error err := cfg.DB().Where("id = ?", req.UserID).First(data).Error
return data, err return data, err
@ -38,17 +38,17 @@ var _ = Router.Get("/", "用户列表", auth.Check("user", "", auth.DoUpdate), c
var _ = Router.Patch("/{user_id}", "更新用户", auth.Check("user", "user_id", auth.DoUpdate, checkOwner), userPatch) var _ = Router.Patch("/{user_id}", "更新用户", auth.Check("user", "user_id", auth.DoUpdate, checkOwner), userPatch)
type patchOpts struct { type patchOpts struct {
ID string `json:"id" parse:"path@user_id"` ID string `json:"id" src:"path@user_id" desc:"用户ID"`
Nickname *string `json:"nickname" parse:"json"` Nickname *string `json:"nickname" src:"json" desc:"昵称"`
Icon *string `json:"icon" parse:"json"` Icon *string `json:"icon" src:"json" desc:"头像"`
Username *string `json:"username" parse:"json"` Username *string `json:"username" src:"json" desc:"用户名"`
Email *string `json:"email" parse:"json"` Email *string `json:"email" src:"json" desc:"邮箱"`
Phone *string `json:"phone" parse:"json"` Phone *string `json:"phone" src:"json" desc:"手机号"`
Status *uint `json:"status" parse:"json"` Status *uint `json:"status" src:"json" desc:"状态"`
} }
func userPatch(x *vigo.X, opts *patchOpts) (any, error) { func userPatch(x *vigo.X, opts *patchOpts) (*models.User, error) {
data := &models.User{} data := &models.User{}
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error err := cfg.DB().Where("id = ?", opts.ID).First(data).Error
if err != nil { if err != nil {

@ -24,6 +24,6 @@ var uifs embed.FS
func init() { func init() {
Router.Extend("v", vyesui.Router) Router.Extend("v", vyesui.Router)
Router.Extend("api", api.Router) Router.Extend("api", api.Router)
Router.SubRouter("/*path").Use(cors.AllowAny) Router.SubRouter("/**").Use(cors.AllowAny)
vyes.WrapUI(Router, uifs) vyes.WrapUI(Router, uifs)
} }

@ -29,13 +29,13 @@ var (
func GenJwt(claim *Claims) (string, error) { func GenJwt(claim *Claims) (string, error) {
return GenJwtWithKey(claim, cfg.Config.Key) return GenJwtWithKey(claim, cfg.Config.Key)
} }
func GenJwtWithKey(claim *Claims, key string) (string, error) { func GenJwtWithKey(claim *Claims, key string) (string, error) {
if claim.ExpiresAt == nil { if claim.ExpiresAt == nil {
claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Hour)) claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Hour))
} }
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
return token.SignedString([]byte(key)) return token.SignedString([]byte(key))
} }
func ParseJwt(tokenString string, keys ...string) (*Claims, error) { func ParseJwt(tokenString string, keys ...string) (*Claims, error) {
@ -60,7 +60,7 @@ func ParseJwt(tokenString string, keys ...string) (*Claims, error) {
return claims, nil return claims, nil
} }
func checkJWT(x *vigo.X) (*Claims, error) { func CheckJWT(x *vigo.X) (*Claims, error) {
authHeader := x.Request.Header.Get("Authorization") authHeader := x.Request.Header.Get("Authorization")
if authHeader == "" { if authHeader == "" {
authHeader = x.Request.URL.Query().Get("Authorization") authHeader = x.Request.URL.Query().Get("Authorization")
@ -82,10 +82,6 @@ func checkJWT(x *vigo.X) (*Claims, error) {
return claims, nil return claims, nil
} }
func CheckJWT(x *vigo.X) (any, error) {
return checkJWT(x)
}
type CustomCheckFunc = func(x *vigo.X, data any) bool 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) { func Check(target string, pid string, l AuthLevel, funcs ...CustomCheckFunc) func(x *vigo.X, data any) (any, error) {
@ -93,7 +89,7 @@ func Check(target string, pid string, l AuthLevel, funcs ...CustomCheckFunc) fun
var err error var err error
claims, ok := x.Get("token").(*Claims) claims, ok := x.Get("token").(*Claims)
if !ok { if !ok {
claims, err = checkJWT(x) claims, err = CheckJWT(x)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -13,27 +13,27 @@ const AUSTATUS_REJECT = "reject"
type App struct { type App struct {
vigo.Model vigo.Model
Name string `json:"name" parse:"json"` Name string `json:"name" src:"json" desc:"应用名称"`
Icon string `json:"icon" parse:"json"` Icon string `json:"icon" src:"json" desc:"图标"`
Des string `json:"des" parse:"json"` Des string `json:"des" src:"json" desc:"描述"`
Typ string `json:"typ" gorm:"default:public" parse:"json"` Typ string `json:"typ" gorm:"default:public" src:"json" desc:"类型"`
Status string `json:"status" gorm:"default:ok" parse:"json"` Status string `json:"status" gorm:"default:ok" src:"json" desc:"状态"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36);default: null" parse:"json"` 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"` InitRole *Role `json:"init_role" gorm:"foreignKey:InitRoleID;references:ID"`
InitUrl string `json:"init_url" parse:"json"` InitUrl string `json:"init_url" src:"json" desc:"初始URL"`
UserCount uint `json:"user_count" default:"0"` UserCount uint `json:"user_count" default:"0"`
Key string `json:"-"` Key string `json:"-"`
} }
type AppUser struct { type AppUser struct {
vigo.Model vigo.Model
AppID string `json:"app_id" parse:"path"` AppID string `json:"app_id" src:"path" desc:"应用ID"`
App *App `json:"app" gorm:"foreignKey:AppID;references:ID"` App *App `json:"app" gorm:"foreignKey:AppID;references:ID"`
UserID string `json:"user_id" parse:"json"` UserID string `json:"user_id" src:"json" desc:"用户ID"`
User *User `json:"user" gorm:"foreignKey:UserID;references:ID"` User *User `json:"user" gorm:"foreignKey:UserID;references:ID"`
Status string `json:"status" parse:"json"` Status string `json:"status" src:"json" desc:"状态"`
} }
func (m *AppUser) onOk(tx *gorm.DB) (err error) { func (m *AppUser) onOk(tx *gorm.DB) (err error) {
@ -70,37 +70,37 @@ func (m *AppUser) AfterUpdate(tx *gorm.DB) error {
type Resource struct { type Resource struct {
vigo.Model vigo.Model
AppID string `json:"app_id" parse:"path@app_id"` AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"`
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
Name string `json:"name" parse:"json"` Name string `json:"name" src:"json" desc:"名称"`
Des string `json:"des" parse:"json"` Des string `json:"des" src:"json" desc:"描述"`
} }
type Role struct { type Role struct {
vigo.Model vigo.Model
AppID string `json:"app_id" parse:"path@app_id"` AppID string `json:"app_id" src:"path@app_id" desc:"应用ID"`
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
Name string `json:"name" parse:"json"` Name string `json:"name" src:"json" desc:"名称"`
Des string `json:"des" parse:"json"` Des string `json:"des" src:"json" desc:"描述"`
UserCount uint `json:"user_count" default:"0"` UserCount uint `json:"user_count" default:"0"`
Access []*Access `json:"-"` Access []*Access `json:"-"`
} }
type Access struct { type Access struct {
vigo.Model vigo.Model
AppID string `json:"app_id" gorm:"index;type:varchar(36)" parse:"path@app_id"` AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"path@app_id" desc:"应用ID"`
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
UserID *string `json:"user_id" gorm:"index;type:varchar(36);default: null" parse:"json"` UserID *string `json:"user_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"用户ID"`
User *User `json:"-" gorm:"foreignKey:UserID;references:ID"` User *User `json:"-" gorm:"foreignKey:UserID;references:ID"`
RoleID *string `json:"role_id" gorm:"index;type:varchar(36);default: null" parse:"json"` RoleID *string `json:"role_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"角色ID"`
Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"` Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"`
ResourceID *string `json:"resource_id" gorm:"index;type:varchar(36);default: null" parse:"json"` ResourceID *string `json:"resource_id" gorm:"index;type:varchar(36);default: null" src:"json" desc:"资源ID"`
Resource *Resource `json:"-" gorm:"foreignKey:ResourceID;references:ID"` Resource *Resource `json:"-" gorm:"foreignKey:ResourceID;references:ID"`
Name string `json:"name" parse:"json"` Name string `json:"name" src:"json" desc:"名称"`
TID string `json:"tid" parse:"json"` TID string `json:"tid" src:"json" desc:"资源ID"`
Level uint `json:"level" parse:"json"` Level uint `json:"level" src:"json" desc:"级别"`
} }

@ -10,7 +10,6 @@ import (
"strings" "strings"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/oauth"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/vyes-ai/vigo" "github.com/vyes-ai/vigo"
@ -21,21 +20,6 @@ var AllModels = &vigo.ModelList{}
func init() { func init() {
AllModels.Append(User{}, AppUser{}, Resource{}, Access{}, Role{}, UserRole{}, Token{}, App{}, SMSCode{}, SMSLog{}) AllModels.Append(User{}, AppUser{}, Resource{}, Access{}, Role{}, UserRole{}, Token{}, App{}, SMSCode{}, SMSLog{})
AllModels.Append(
oauth.User{},
oauth.UserLoginLog{},
oauth.OAuthClient{},
oauth.OAuthAuthorizationCode{},
oauth.OAuthAccessToken{},
oauth.OAuthRefreshToken{},
oauth.OAuthScope{},
oauth.OAuthClientScope{},
oauth.OAuthProvider{},
oauth.OAuthAccount{},
oauth.UserToken{},
oauth.UserSession{},
oauth.OAuthUserConsent{},
)
} }
func Migrate() error { func Migrate() error {
@ -88,5 +72,5 @@ func InitDB() error {
if app.InitRoleID == nil { if app.InitRoleID == nil {
logv.AssertError(cfg.DB().Model(app).Update("init_role_id", adminID).Error) logv.AssertError(cfg.DB().Model(app).Update("init_role_id", adminID).Error)
} }
return oauth.InitializeOAuthData(cfg.DB()) return nil
} }

@ -1,8 +1,9 @@
package models package models
import ( import (
"github.com/vyes-ai/vigo"
"time" "time"
"github.com/vyes-ai/vigo"
) )
// refresh token由oa 秘钥签发,有效期长, 存储在token表 // refresh token由oa 秘钥签发,有效期长, 存储在token表
@ -10,12 +11,12 @@ import (
// OverPerm 非oa应用获取oa数据的权限由用户设定 // OverPerm 非oa应用获取oa数据的权限由用户设定
type Token struct { type Token struct {
vigo.Model vigo.Model
UserID string `json:"user_id" gorm:"index;type:varchar(36)" parse:"json"` UserID string `json:"user_id" gorm:"index;type:varchar(36)" src:"json" desc:"用户ID"`
User *User `json:"-"` User *User `json:"-"`
AppID string `json:"app_id" gorm:"index;type:varchar(36)" parse:"json"` AppID string `json:"app_id" gorm:"index;type:varchar(36)" src:"json" desc:"应用ID"`
App *App `json:"-"` App *App `json:"-"`
ExpiredAt time.Time `json:"expired_at" parse:"json"` ExpiredAt time.Time `json:"expired_at" src:"json" desc:"过期时间"`
OverPerm string `json:"over_perm" parse:"json"` OverPerm string `json:"over_perm" src:"json" desc:"覆盖权限"`
Device string `json:"device" parse:"json"` Device string `json:"device" src:"json" desc:"设备信息"`
Ip string `json:"ip"` Ip string `json:"ip"`
} }

@ -10,29 +10,29 @@ import (
// code 64 hex / 32 byte / 256 bit // code 64 hex / 32 byte / 256 bit
type User struct { type User struct {
vigo.Model vigo.Model
Username string `json:"username" gorm:"type:varchar(100);unique;default:not null" parse:"json"` Username string `json:"username" gorm:"type:varchar(100);unique;default:not null" src:"json" desc:"用户名"`
Nickname string `json:"nickname" gorm:"type:varchar(100)" parse:"json"` Nickname string `json:"nickname" gorm:"type:varchar(100)" src:"json" desc:"昵称"`
Icon string `json:"icon" parse:"json"` Icon string `json:"icon" src:"json" desc:"头像"`
Email string `json:"email" gorm:"unique;type:varchar(64);null;default:null" parse:"json"` 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" parse:"json"` 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" parse:"json"` Region string `json:"region" gorm:"type:varchar(32);default:null" src:"json" desc:"地区"`
Status uint `json:"status" parse:"json"` Status uint `json:"status" src:"json" desc:"状态"`
Salt string `json:"-" gorm:"type:varchar(32)"` Salt string `json:"-" gorm:"type:varchar(32)"`
Code string `json:"-" gorm:"type:varchar(64)" parse:"json"` Code string `json:"-" gorm:"type:varchar(64)" src:"json" desc:"代码"`
} }
type UserRole struct { type UserRole struct {
vigo.Model vigo.Model
UserID string `json:"user_id" parse:"path@user_id"` UserID string `json:"user_id" src:"path@user_id" desc:"用户ID"`
User *User `json:"-" gorm:"foreignKey:UserID;references:ID"` User *User `json:"-" gorm:"foreignKey:UserID;references:ID"`
RoleID string `json:"role_id" parse:"json"` RoleID string `json:"role_id" src:"json" desc:"角色ID"`
Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"` Role *Role `json:"-" gorm:"foreignKey:RoleID;references:ID"`
AppID string `json:"app_id" parse:"json"` AppID string `json:"app_id" src:"json" desc:"应用ID"`
App *App `json:"-" gorm:"foreignKey:AppID;references:ID"` App *App `json:"-" gorm:"foreignKey:AppID;references:ID"`
Status string `json:"status"` Status string `json:"status"`

@ -7,21 +7,22 @@
package oauth package oauth
import ( import (
"time"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/vyes-ai/vigo" "github.com/vyes-ai/vigo"
"gorm.io/gorm" "gorm.io/gorm"
"time"
) )
// AuthorizeRequest 授权请求参数 // AuthorizeRequest 授权请求参数
type AuthorizeRequest struct { type AuthorizeRequest struct {
ResponseType string `json:"response_type" parse:"query"` ResponseType string `json:"response_type" src:"query" desc:"响应类型"`
ClientID string `json:"client_id" parse:"query"` ClientID string `json:"client_id" src:"query" desc:"客户端ID"`
RedirectURI string `json:"redirect_uri" parse:"query"` RedirectURI string `json:"redirect_uri" src:"query" desc:"重定向URI"`
Scope string `json:"scope" parse:"query"` Scope string `json:"scope" src:"query" desc:"授权范围"`
State string `json:"state" parse:"query"` State string `json:"state" src:"query" desc:"状态"`
CodeChallenge string `json:"code_challenge" parse:"query"` CodeChallenge string `json:"code_challenge" src:"query" desc:"代码挑战"`
CodeChallengeMethod string `json:"code_challenge_method" parse:"query"` CodeChallengeMethod string `json:"code_challenge_method" src:"query" desc:"代码挑战方法"`
} }
// AuthorizeResponse 授权响应 // AuthorizeResponse 授权响应
@ -34,7 +35,7 @@ type AuthorizeResponse struct {
} }
// handleAuthorize 处理OAuth授权请求 // handleAuthorize 处理OAuth授权请求
func handleAuthorize(x *vigo.X, args *AuthorizeRequest) (any, error) { func handleAuthorize(x *vigo.X, args *AuthorizeRequest) (*AuthorizeResponse, error) {
db := cfg.DB() db := cfg.DB()
// 1. 验证响应类型 // 1. 验证响应类型

@ -13,10 +13,10 @@ import (
// RevokeRequest 撤销令牌请求参数 // RevokeRequest 撤销令牌请求参数
type RevokeRequest struct { type RevokeRequest struct {
Token string `parse:"form" binding:"required"` Token string `src:"form" desc:"令牌" binding:"required"`
TokenTypeHint string `parse:"form"` // access_token 或 refresh_token TokenTypeHint string `src:"form" desc:"令牌类型提示"` // access_token 或 refresh_token
ClientID string `parse:"form"` ClientID string `src:"form" desc:"客户端ID"`
ClientSecret string `parse:"form"` ClientSecret string `src:"form" desc:"客户端密钥"`
} }
// RevokeResponse 撤销令牌响应 // RevokeResponse 撤销令牌响应
@ -26,7 +26,7 @@ type RevokeResponse struct {
} }
// handleRevoke 处理OAuth撤销请求 // handleRevoke 处理OAuth撤销请求
func handleRevoke(x *vigo.X, args *RevokeRequest) (any, error) { func handleRevoke(x *vigo.X, args *RevokeRequest) (*RevokeResponse, error) {
if args.Token == "" { if args.Token == "" {
return nil, vigo.NewError("令牌不能为空").WithCode(400) return nil, vigo.NewError("令牌不能为空").WithCode(400)
} }

@ -10,25 +10,26 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"time"
"github.com/veypi/OneAuth/cfg" "github.com/veypi/OneAuth/cfg"
"github.com/vyes-ai/vigo" "github.com/vyes-ai/vigo"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gorm.io/gorm" "gorm.io/gorm"
"time"
) )
// TokenRequest 令牌请求参数 // TokenRequest 令牌请求参数
type TokenRequest struct { type TokenRequest struct {
GrantType string `json:"grant_type" parse:"form"` GrantType string `json:"grant_type" src:"form" desc:"授权类型"`
Code string `json:"code" parse:"form"` Code string `json:"code" src:"form" desc:"授权码"`
RedirectURI string `json:"redirect_uri" parse:"form"` RedirectURI string `json:"redirect_uri" src:"form" desc:"重定向URI"`
ClientID string `json:"client_id" parse:"form"` ClientID string `json:"client_id" src:"form" desc:"客户端ID"`
ClientSecret string `json:"client_secret" parse:"form"` ClientSecret string `json:"client_secret" src:"form" desc:"客户端密钥"`
RefreshToken string `json:"refresh_token" parse:"form"` RefreshToken string `json:"refresh_token" src:"form" desc:"刷新令牌"`
CodeVerifier string `json:"code_verifier" parse:"form"` CodeVerifier string `json:"code_verifier" src:"form" desc:"PKCE验证码"`
Username string `json:"username" parse:"form"` // for password grant Username string `json:"username" src:"form" desc:"用户名"` // for password grant
Password string `json:"password" parse:"form"` // for password grant Password string `json:"password" src:"form" desc:"密码"` // for password grant
Scope string `json:"scope" parse:"form"` // for password grant Scope string `json:"scope" src:"form" desc:"权限范围"` // for password grant
} }
// TokenResponse 令牌响应 // TokenResponse 令牌响应
@ -41,7 +42,7 @@ type TokenResponse struct {
} }
// handleToken 处理OAuth令牌请求 // handleToken 处理OAuth令牌请求
func handleToken(x *vigo.X, args *TokenRequest) (any, error) { func handleToken(x *vigo.X, args *TokenRequest) (*TokenResponse, error) {
db := cfg.DB() db := cfg.DB()
switch args.GrantType { switch args.GrantType {
@ -57,138 +58,138 @@ func handleToken(x *vigo.X, args *TokenRequest) (any, error) {
} }
// handleAuthorizationCodeGrant 处理授权码授权类型 // handleAuthorizationCodeGrant 处理授权码授权类型
func handleAuthorizationCodeGrant(db *gorm.DB, x *vigo.X, args *TokenRequest) error { func handleAuthorizationCodeGrant(db *gorm.DB, x *vigo.X, args *TokenRequest) (*TokenResponse, error) {
// 1. 验证授权码 // 1. 验证授权码
var authCode OAuthAuthorizationCode var authCode OAuthAuthorizationCode
if err := db.Where("code = ? AND used = ?", args.Code, false).First(&authCode).Error; err != nil { if err := db.Where("code = ? AND used = ?", args.Code, false).First(&authCode).Error; err != nil {
return vigo.NewError("无效的授权码").WithCode(400) return nil, vigo.NewError("无效的授权码").WithCode(400)
} }
// 2. 检查授权码是否过期 // 2. 检查授权码是否过期
if authCode.IsExpired() { if authCode.IsExpired() {
return vigo.NewError("授权码已过期").WithCode(400) return nil, vigo.NewError("授权码已过期").WithCode(400)
} }
// 3. 验证客户端 // 3. 验证客户端
var client OAuthClient var client OAuthClient
if err := db.Where("id = ? AND client_id = ?", authCode.ClientID, args.ClientID).First(&client).Error; err != nil { if err := db.Where("id = ? AND client_id = ?", authCode.ClientID, args.ClientID).First(&client).Error; err != nil {
return vigo.NewError("无效的客户端").WithCode(400) return nil, vigo.NewError("无效的客户端").WithCode(400)
} }
// 4. 验证客户端密钥(对于机密客户端) // 4. 验证客户端密钥(对于机密客户端)
if !client.IsPublic && client.ClientSecret != args.ClientSecret { if !client.IsPublic && client.ClientSecret != args.ClientSecret {
return vigo.NewError("无效的客户端凭据").WithCode(400) return nil, vigo.NewError("无效的客户端凭据").WithCode(400)
} }
// 5. 验证重定向URI // 5. 验证重定向URI
if authCode.RedirectURI != args.RedirectURI { if authCode.RedirectURI != args.RedirectURI {
return vigo.NewError("重定向URI不匹配").WithCode(400) return nil, vigo.NewError("重定向URI不匹配").WithCode(400)
} }
// 6. 验证PKCE如果使用 // 6. 验证PKCE如果使用
if authCode.CodeChallenge != "" { if authCode.CodeChallenge != "" {
if err := validatePKCE(authCode.CodeChallenge, authCode.CodeChallengeMethod, args.CodeVerifier); err != nil { if err := validatePKCE(authCode.CodeChallenge, authCode.CodeChallengeMethod, args.CodeVerifier); err != nil {
return vigo.NewError("PKCE验证失败").WithError(err).WithCode(400) return nil, vigo.NewError("PKCE验证失败").WithError(err).WithCode(400)
} }
} }
// 7. 标记授权码为已使用 // 7. 标记授权码为已使用
if err := db.Model(&authCode).Update("used", true).Error; err != nil { if err := db.Model(&authCode).Update("used", true).Error; err != nil {
return vigo.NewError("授权码更新失败").WithError(err).WithCode(500) return nil, vigo.NewError("授权码更新失败").WithError(err).WithCode(500)
} }
// 8. 生成访问令牌 // 8. 生成访问令牌
accessToken, err := generateAccessToken(db, &client, authCode.UserID, authCode.Scope) accessToken, err := generateAccessToken(db, &client, authCode.UserID, authCode.Scope)
if err != nil { if err != nil {
return vigo.NewError("访问令牌生成失败").WithError(err).WithCode(500) return nil, vigo.NewError("访问令牌生成失败").WithError(err).WithCode(500)
} }
// 9. 生成刷新令牌 // 9. 生成刷新令牌
refreshToken, err := generateRefreshToken(db, accessToken, &client, authCode.UserID, authCode.Scope) refreshToken, err := generateRefreshToken(db, accessToken, &client, authCode.UserID, authCode.Scope)
if err != nil { if err != nil {
return vigo.NewError("刷新令牌生成失败").WithError(err).WithCode(500) return nil, vigo.NewError("刷新令牌生成失败").WithError(err).WithCode(500)
} }
return x.JSON(&TokenResponse{ return &TokenResponse{
AccessToken: accessToken.Token, AccessToken: accessToken.Token,
TokenType: TokenTypeBearer, TokenType: TokenTypeBearer,
ExpiresIn: int64(DefaultAccessTokenExpiry.Seconds()), ExpiresIn: int64(DefaultAccessTokenExpiry.Seconds()),
RefreshToken: refreshToken.Token, RefreshToken: refreshToken.Token,
Scope: authCode.Scope, Scope: authCode.Scope,
}) }, nil
} }
// handleRefreshTokenGrant 处理刷新令牌授权类型 // handleRefreshTokenGrant 处理刷新令牌授权类型
func handleRefreshTokenGrant(db *gorm.DB, x *vigo.X, args *TokenRequest) error { func handleRefreshTokenGrant(db *gorm.DB, x *vigo.X, args *TokenRequest) (*TokenResponse, error) {
// 1. 验证刷新令牌 // 1. 验证刷新令牌
var refreshToken OAuthRefreshToken var refreshToken OAuthRefreshToken
if err := db.Where("token = ? AND revoked = ?", args.RefreshToken, false).First(&refreshToken).Error; err != nil { if err := db.Where("token = ? AND revoked = ?", args.RefreshToken, false).First(&refreshToken).Error; err != nil {
return vigo.NewError("无效的刷新令牌").WithCode(400) return nil, vigo.NewError("无效的刷新令牌").WithCode(400)
} }
// 2. 检查刷新令牌是否过期 // 2. 检查刷新令牌是否过期
if refreshToken.IsExpired() { if refreshToken.IsExpired() {
return vigo.NewError("刷新令牌已过期").WithCode(400) return nil, vigo.NewError("刷新令牌已过期").WithCode(400)
} }
// 3. 验证客户端 // 3. 验证客户端
var client OAuthClient var client OAuthClient
if err := db.Where("id = ? AND client_id = ?", refreshToken.ClientID, args.ClientID).First(&client).Error; err != nil { if err := db.Where("id = ? AND client_id = ?", refreshToken.ClientID, args.ClientID).First(&client).Error; err != nil {
return vigo.NewError("无效的客户端").WithCode(400) return nil, vigo.NewError("无效的客户端").WithCode(400)
} }
// 4. 撤销旧的访问令牌 // 4. 撤销旧的访问令牌
if err := db.Model(&OAuthAccessToken{}).Where("id = ?", refreshToken.AccessTokenID).Update("revoked", true).Error; err != nil { if err := db.Model(&OAuthAccessToken{}).Where("id = ?", refreshToken.AccessTokenID).Update("revoked", true).Error; err != nil {
return vigo.NewError("旧令牌撤销失败").WithError(err).WithCode(500) return nil, vigo.NewError("旧令牌撤销失败").WithError(err).WithCode(500)
} }
// 5. 生成新的访问令牌 // 5. 生成新的访问令牌
accessToken, err := generateAccessToken(db, &client, refreshToken.UserID, refreshToken.Scope) accessToken, err := generateAccessToken(db, &client, refreshToken.UserID, refreshToken.Scope)
if err != nil { if err != nil {
return vigo.NewError("访问令牌生成失败").WithError(err).WithCode(500) return nil, vigo.NewError("访问令牌生成失败").WithError(err).WithCode(500)
} }
// 6. 更新刷新令牌关联 // 6. 更新刷新令牌关联
if err := db.Model(&refreshToken).Update("access_token_id", accessToken.ID).Error; err != nil { if err := db.Model(&refreshToken).Update("access_token_id", accessToken.ID).Error; err != nil {
return vigo.NewError("刷新令牌更新失败").WithError(err).WithCode(500) return nil, vigo.NewError("刷新令牌更新失败").WithError(err).WithCode(500)
} }
return x.JSON(&TokenResponse{ return &TokenResponse{
AccessToken: accessToken.Token, AccessToken: accessToken.Token,
TokenType: TokenTypeBearer, TokenType: TokenTypeBearer,
ExpiresIn: int64(DefaultAccessTokenExpiry.Seconds()), ExpiresIn: int64(DefaultAccessTokenExpiry.Seconds()),
RefreshToken: refreshToken.Token, RefreshToken: refreshToken.Token,
Scope: refreshToken.Scope, Scope: refreshToken.Scope,
}) }, nil
} }
// handlePasswordGrant 处理密码授权类型 // handlePasswordGrant 处理密码授权类型
func handlePasswordGrant(db *gorm.DB, x *vigo.X, args *TokenRequest) error { func handlePasswordGrant(db *gorm.DB, x *vigo.X, args *TokenRequest) (*TokenResponse, error) {
// 1. 验证必要参数 // 1. 验证必要参数
if args.Username == "" || args.Password == "" { if args.Username == "" || args.Password == "" {
return vigo.NewError("用户名和密码不能为空").WithCode(400) return nil, vigo.NewError("用户名和密码不能为空").WithCode(400)
} }
// 2. 验证客户端 // 2. 验证客户端
var client OAuthClient var client OAuthClient
if err := db.Where("client_id = ?", args.ClientID).First(&client).Error; err != nil { if err := db.Where("client_id = ?", args.ClientID).First(&client).Error; err != nil {
return vigo.NewError("无效的客户端").WithCode(400) return nil, vigo.NewError("无效的客户端").WithCode(400)
} }
// 3. 验证客户端密钥(对于机密客户端) // 3. 验证客户端密钥(对于机密客户端)
if !client.IsPublic && client.ClientSecret != args.ClientSecret { if !client.IsPublic && client.ClientSecret != args.ClientSecret {
return vigo.NewError("无效的客户端凭据").WithCode(400) return nil, vigo.NewError("无效的客户端凭据").WithCode(400)
} }
// 4. 验证用户凭据 // 4. 验证用户凭据
var user User var user User
if err := db.Where("username = ?", args.Username).First(&user).Error; err != nil { if err := db.Where("username = ?", args.Username).First(&user).Error; err != nil {
return vigo.NewError("用户名或密码错误").WithCode(400) return nil, vigo.NewError("用户名或密码错误").WithCode(400)
} }
// 5. 验证密码 // 5. 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(args.Password)); err != nil { if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(args.Password)); err != nil {
return vigo.NewError("用户名或密码错误").WithCode(400) return nil, vigo.NewError("用户名或密码错误").WithCode(400)
} }
// 6. 处理权限范围 // 6. 处理权限范围
@ -200,22 +201,22 @@ func handlePasswordGrant(db *gorm.DB, x *vigo.X, args *TokenRequest) error {
// 7. 生成访问令牌 // 7. 生成访问令牌
accessToken, err := generateAccessToken(db, &client, user.ID, scope) accessToken, err := generateAccessToken(db, &client, user.ID, scope)
if err != nil { if err != nil {
return vigo.NewError("访问令牌生成失败").WithError(err).WithCode(500) return nil, vigo.NewError("访问令牌生成失败").WithError(err).WithCode(500)
} }
// 8. 生成刷新令牌 // 8. 生成刷新令牌
refreshToken, err := generateRefreshToken(db, accessToken, &client, user.ID, scope) refreshToken, err := generateRefreshToken(db, accessToken, &client, user.ID, scope)
if err != nil { if err != nil {
return vigo.NewError("刷新令牌生成失败").WithError(err).WithCode(500) return nil, vigo.NewError("刷新令牌生成失败").WithError(err).WithCode(500)
} }
return x.JSON(&TokenResponse{ return &TokenResponse{
AccessToken: accessToken.Token, AccessToken: accessToken.Token,
TokenType: TokenTypeBearer, TokenType: TokenTypeBearer,
ExpiresIn: int64(DefaultAccessTokenExpiry.Seconds()), ExpiresIn: int64(DefaultAccessTokenExpiry.Seconds()),
RefreshToken: refreshToken.Token, RefreshToken: refreshToken.Token,
Scope: scope, Scope: scope,
}) }, nil
} }
// 辅助函数 // 辅助函数

@ -1,343 +1,186 @@
:root { :root {
/* 颜色 */ /* --- Semantics: Theme (语义主题) --- */
--color-primary: #92a5ff; /* Primary Action */
--color-secondary: #3f37c9; --color-primary: #4361ee;
--color-success: #28a745; --color-primary-hover: #2563eb;
--color-danger: #dc3545; --color-primary-active: #1d4ed8;
--color-warning: #ffc107; --color-primary-text: #fff;
--color-info: #17a2b8;
/* Secondary Action */
--color-text: #333; --color-secondary: #3f37c9;
--color-text-light: #555; --color-secondary-hover: #3730a3;
--color-background: #f1f8ff; --color-secondary-active: #332d92;
--color-border: #dee2e6; --color-secondary-text: #fff;
--color-link: var(--color-primary);
--color-link-hover: #0056b3; /* Feedback */
--color-info: #0ea5e9;
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --color-success: #22c55e;
--font-family-serif: "Georgia", serif; --color-warning: #fbbf24;
--font-family-mono: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --color-danger: #ef4444;
--font-size-base: 1rem; --color-danger-hover: #dc2626;
--font-weight-normal: 400;
--font-weight-bold: 700; /* Backgrounds */
--bg-color: #f1f8ff;
/* 间距 */ --bg-color-secondary: #f7fcff;
--spacing-xs: 0.25rem; --bg-color-tertiary: #e0edf3;
--spacing-sm: 0.5rem;
--spacing-md: 1rem; /* Text */
--spacing-lg: 1.5rem; --text-color: #111827;
--spacing-xl: 2rem; --text-color-secondary: #374151;
--text-color-tertiary: #6b7280;
--border-radius: 0.25rem; --text-color-disabled: #9ca3af;
--box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); --text-color-inverse: #f1f5f9;
--transition: all 0.3s ease-in-out;
} /* Borders */
--border-color: #d1d5db;
*, --border-color-hover: #9ca3af;
*::before,
*::after { /* --- Dimensions: Spacing & Radius (尺寸) --- */
box-sizing: border-box; --radius-sm: 2px;
} --radius-md: 4px;
--radius-lg: 8px;
html, --radius-xl: 12px;
body, --radius-full: 9999px;
h1,
h2, --spacing-xs: 4px;
h3, --spacing-sm: 8px;
h4, --spacing-md: 16px;
h5, --spacing-lg: 24px;
h6, --spacing-xl: 32px;
p,
figure, /* --- Typography (排版) --- */
blockquote, --font-size-xs: 12px;
dl, --font-size-sm: 13px;
dd { --font-size-md: 14px;
margin: 0; /* Base size */
padding: 0; --font-size-lg: 16px;
} --font-size-xl: 20px;
--font-size-2xl: 24px;
html,
body { --font-weight-normal: 400;
width: 100vw; --font-weight-medium: 500;
height: 100vh; --font-weight-bold: 600;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; /* --- Effects (特效) --- */
scroll-behavior: smooth; --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
font-family: var(--font-family-sans); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
font-size: var(--font-size-base);
line-height: 1.5; /* --- Transitions (动画) --- */
color: var(--color-text); --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
background-color: var(--color-background); --transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
} --transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
html {
overflow: hidden; *,
} *::before,
*::after {
body { box-sizing: border-box;
overflow: auto; margin: 0;
} padding: 0;
}
ul,
ol { body {
list-style: none; width: 100vw;
padding: 0; height: 100vh;
margin: 0; font-family: var(--font-family, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif);
} font-size: var(--font-size-md);
line-height: 1.5;
img, color: var(--text-color);
picture, background-color: var(--bg-color);
video, -webkit-font-smoothing: antialiased;
canvas, -moz-osx-font-smoothing: grayscale;
svg { }
display: block;
max-width: 100%; body[theme='dark'] {
} --bg-color: #0f172a;
--bg-color-secondary: #1e293b;
input, --bg-color-tertiary: #334155;
button,
textarea, --text-color: #f1f5f9;
select { --text-color-secondary: #94a3b8;
font: inherit; --text-color-tertiary: #64748b;
margin: 0; --text-color-inverse: #000000;
}
--border-color: #334155;
p, --border-color-hover: #475569;
h1,
h2, --color-primary: #60a5fa;
h3, --color-primary-hover: #3b82f6;
h4, --color-primary-active: #2563eb;
h5,
h6 { --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.5);
overflow-wrap: break-word; --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.5);
} --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.5);
}
h1, body[theme='cyberpunk'] {
.h1 { /* Dark blue-purple background */
font-size: 2.5rem; --bg-color: #0b0c15;
font-weight: var(--font-weight-bold); --bg-color-secondary: #161826;
line-height: 1.2; --bg-color-tertiary: #23253a;
margin-bottom: var(--spacing-md);
} /* Main text white-ish for readability, accents in neon */
--text-color: #e2e8f0;
h2, --text-color-secondary: #94a3b8;
.h2 { --text-color-tertiary: #64748b;
font-size: 2rem; --text-color-inverse: #0b0c15;
font-weight: var(--font-weight-bold);
line-height: 1.2; /* Subtle border with neon hover */
margin-bottom: var(--spacing-md); --border-color: #2d2f45;
} --border-color-hover: #ff00ff;
h3, /* Neon Pink Primary */
.h3 { --color-primary: #d946ef;
font-size: 1.75rem; --color-primary-hover: #e879f9;
font-weight: var(--font-weight-bold); --color-primary-active: #c026d3;
line-height: 1.2; --color-primary-text: #ffffff;
margin-bottom: var(--spacing-md);
} /* Neon Cyan Secondary */
--color-secondary: #06b6d4;
h4, --color-secondary-hover: #22d3ee;
.h4 { --color-secondary-active: #0891b2;
font-size: 1.5rem; --color-secondary-text: #ffffff;
font-weight: var(--font-weight-bold);
line-height: 1.2; /* Status colors */
margin-bottom: var(--spacing-sm); --color-success: #34d399;
} --color-warning: #facc15;
--color-danger: #fb7185;
h5, --color-info: #38bdf8;
.h5 {
font-size: 1.25rem; /* Slightly squared but not harsh */
font-weight: var(--font-weight-bold); --radius-sm: 2px;
line-height: 1.2; --radius-md: 4px;
margin-bottom: var(--spacing-sm); --radius-lg: 6px;
} --radius-xl: 8px;
--radius-full: 9999px;
h6,
.h6 { /* Glow effects */
font-size: 1rem; --shadow-sm: 0 0 8px -2px rgba(217, 70, 239, 0.3);
font-weight: var(--font-weight-bold); --shadow-md: 0 0 15px -3px rgba(6, 182, 212, 0.3);
line-height: 1.2; --shadow-lg: 0 0 25px -5px rgba(217, 70, 239, 0.4);
margin-bottom: var(--spacing-sm); }
}
body[theme='forest'] {
p { --bg-color: #f1f8e9;
margin-bottom: var(--spacing-md); --bg-color-secondary: #dcedc8;
line-height: 1.6; --bg-color-tertiary: #c5e1a5;
}
--text-color: #1b5e20;
--text-color-secondary: #33691e;
hr { --text-color-tertiary: #558b2f;
border: 0;
border-top: 1px solid var(--color-border); --border-color: #aed581;
margin: var(--spacing-lg) 0; --border-color-hover: #8bc34a;
}
--color-primary: #2e7d32;
--color-primary-hover: #1b5e20;
.container {
width: 100%; --color-success: #4caf50;
max-width: 1200px; --color-warning: #afb42b;
margin-left: auto;
margin-right: auto; --radius-sm: 6px;
padding-left: var(--spacing-md); --radius-md: 12px;
padding-right: var(--spacing-md); --radius-lg: 18px;
} --radius-xl: 24px;
}
@media (max-width: 768px) {
.container {
padding-left: var(--spacing-sm);
padding-right: var(--spacing-sm);
}
}
.break-word {
word-wrap: break-word;
overflow-wrap: break-word;
}
.clearfix::after {
content: "";
display: table;
clear: both;
}
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
i.fa-solid {
cursor: pointer;
}
i.fa-solid:hover {
opacity: 0.7;
}
::-webkit-scrollbar {
background-color: none;
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
background: #aaa;
}
/* 新增扁平化和对比度样式 */
/* 按钮基础样式 */
button {
background-color: var(--color-primary);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
button:hover {
background-color: #2563eb;
/* A slightly darker primary color */
box-shadow: 0 2px 4px var(--shadow-color);
}
button:active {
background-color: #1e40af;
/* Even darker */
}
button:disabled {
background-color: var(--color-secondary);
cursor: not-allowed;
}
/* 表单元素基础样式 */
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
textarea {
border: 1px solid var(--color-border);
border-radius: 4px;
color: var(--text-color);
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="number"]:focus,
textarea:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
/* primary-color with transparency */
}
/* 通用容器和阴影 */
.card {
background-color: white;
border-radius: 8px;
box-shadow: 0 1px 3px var(--shadow-color), 0 1px 2px var(--shadow-color);
padding: 20px;
margin-bottom: 20px;
}
/* 辅助类 */
.text-primary {
color: var(--color-primary);
}
.text-secondary {
color: var(--color-secondary);
}
.text-success {
color: var(--color-success);
}
.text-warning {
color: var(--color-warning);
}
.text-error {
color: var(--color-danger);
}
.bg-primary {
background-color: var(--color-primary);
}
.bg-secondary {
background-color: var(--color-secondary);
}
.bg-success {
background-color: var(--color-success);
}
.bg-warning {
background-color: var(--color-warning);
}
.bg-error {
background-color: var(--color-danger);
}
.border-primary {
border-color: var(--color-primary);
}
.hover-underline:hover {
text-decoration: underline;
}

@ -0,0 +1,82 @@
/* 用于定义第三库的样式变量 */
body {
--v-color-primary: var(--color-primary);
--v-color-primary-hover: var(--color-primary-hover);
--v-color-primary-active: var(--color-primary-active);
--v-color-primary-text: var(--color-primary-text);
--v-color-secondary: var(--color-secondary);
--v-color-secondary-hover: var(--color-secondary-hover);
--v-color-secondary-active: var(--color-secondary-active);
--v-color-secondary-text: var(--color-secondary-text);
/* Feedback */
--v-color-info: var(--color-info);
--v-color-success: var(--color-success);
--v-color-warning: var(--color-warning);
--v-color-danger: var(--color-danger);
--v-color-danger-hover: var(--color-danger-hover);
/* Backgrounds */
--v-bg-color: var(--bg-color);
--v-bg-color-secondary: var(--bg-color-secondary);
--v-bg-color-tertiary: var(--bg-color-tertiary);
/* Text */
--v-text-color: var(--text-color);
--v-text-color-secondary: var(--text-color-secondary);
--v-text-color-tertiary: var(--text-color-tertiary);
--v-text-color-disabled: var(--text-color-disabled);
--v-text-color-inverse: var(--text-color-inverse);
/* Borders */
--v-border-color: var(--border-color);
--v-border-color-hover: var(--border-color-hover);
}
/* 自定义可视化流程图的暗黑主题变量 */
body {
--vf-bg-color: var(--bg-color-secondary) !important;
--vf-grid-color: var(--border-color) !important;
--vf-node-bg: var(--bg-color-secondary) !important;
--vf-node-border: var(--border-color) !important;
--vf-node-header-bg: var(--bg-color-tertiary) !important;
--vf-text-color: var(--text-color) !important;
--vf-port-color: var(--color-primary) !important;
--vf-port-hover: var(--color-primary-hover) !important;
--vf-edge-color: #888888 !important;
--vf-edge-selected: var(--color-primary) !important;
--vf-shadow-color: rgba(0, 0, 0, 0.1) !important;
}
/* 图标交互效果 */
i.fa-solid {
cursor: pointer;
}
i.fa-solid:hover {
opacity: 0.7;
}
/* Scrollbar styling */
::-webkit-scrollbar {
background-color: transparent;
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-color-secondary);
}
Loading…
Cancel
Save