update to new vigo version

v3
veypi 2 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 {
AppID string `json:"app_id" parse:"json"`
UserID *string `json:"user_id" parse:"json"`
RoleID *string `json:"role_id" parse:"json"`
ResourceID *string `json:"resource_id" parse:"json"`
Name string `json:"name" parse:"json"`
TID string `json:"tid" parse:"json"`
Level uint `json:"level" parse:"json"`
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) (any, error) {
func createAccess(x *vigo.X, opts *createOpts) (*models.Access, error) {
// 创建新记录
access := models.Access{
AppID: opts.AppID,

@ -7,18 +7,22 @@
package access
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type deleteOpts struct {
ID string `parse:"path@id"`
ID string `src:"path@id" desc:"记录ID"`
}
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
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 map[string]string{"message": "删除成功"}, nil
return &deleteResponse{Message: "删除成功"}, nil
}

@ -13,12 +13,12 @@ import (
)
type getIDReq struct {
ID string `parse:"path@id"`
ID string `src:"path@id" desc:"记录ID"`
}
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
err := cfg.DB().Where("id = ?", req.ID).First(&access).Error

@ -13,12 +13,12 @@ import (
)
type listOpts struct {
Page int `parse:"query" default:"1"`
PageSize int `parse:"query" default:"20"`
AppID string `parse:"query"`
UserID string `parse:"query"`
RoleID string `parse:"query"`
Name string `parse:"query"`
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 {
@ -28,7 +28,7 @@ type listResponse struct {
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{})
if opts.AppID != "" {

@ -13,15 +13,15 @@ import (
)
type updateOpts struct {
ID string `parse:"path@id"`
Name *string `json:"name" parse:"json"`
TID *string `json:"tid" parse:"json"`
Level *uint `json:"level" parse:"json"`
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) (any, error) {
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 {

@ -14,12 +14,12 @@ import (
)
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)
func appKey(x *vigo.X, req *appIDReq) (any, error) {
func appKey(x *vigo.X, req *appIDReq) (string, error) {
data := &models.App{}
data.ID = req.AppID
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)
func appDelete(x *vigo.X, req *appIDReq) (any, error) {
func appDelete(x *vigo.X, req *appIDReq) (*models.App, error) {
data := &models.App{}
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)
func appGet(x *vigo.X, req *appIDReq) (any, error) {
func appGet(x *vigo.X, req *appIDReq) (*models.App, error) {
data := &models.App{}
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 {
Name *string `json:"name" parse:"query"`
Name *string `json:"name" src:"query" desc:"应用名称"`
}
var _ = Router.Get("/", "获取应用列表", appList)
func appList(x *vigo.X, opts *listOpts) (any, error) {
data := make([]*struct {
models.App
UserStatus string `json:"user_status"`
}, 0, 10)
tokenAny, err := auth.CheckJWT(x)
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 {
token := tokenAny.(*auth.Claims)
uid := token.UID
query := cfg.DB().Table("apps").Select("apps.*,app_users.status user_status")
if opts.Name != nil {
@ -76,17 +77,17 @@ func appList(x *vigo.X, opts *listOpts) (any, error) {
}
type postOpts struct {
Name string `json:"name" parse:"json"`
Icon string `json:"icon" parse:"json"`
Des *string `json:"des" parse:"json"`
Typ string `json:"typ" parse:"json"`
Status string `json:"status" parse:"json"`
InitUrl string `json:"init_url" parse:"json"`
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) (any, error) {
func appPost(x *vigo.X, opts *postOpts) (*models.App, error) {
data := &models.App{}
data.Name = opts.Name
data.Icon = opts.Icon
@ -119,19 +120,19 @@ func appPost(x *vigo.X, opts *postOpts) (any, error) {
}
type patchOpts struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)" parse:"path@app_id"`
Name *string `json:"name" parse:"json"`
Icon *string `json:"icon" parse:"json"`
Des *string `json:"des" parse:"json"`
Typ *string `json:"typ" gorm:"default:auto" parse:"json"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36)" parse:"json"`
Status *string `json:"status" gorm:"default:ok" parse:"json"`
InitUrl *string `json:"init_url" parse:"json"`
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) (any, error) {
func appPatch(x *vigo.X, opts *patchOpts) (*models.App, error) {
data := &models.App{}
err := cfg.DB().Where("id = ?", opts.ID).First(data).Error

@ -7,14 +7,14 @@ import (
)
type createOpts struct {
AppID string `parse:"path@app_id"`
UserID string `json:"user_id" parse:"json"`
Status string `json:"status" parse:"json" default:"ok"`
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) (any, error) {
func createAppUser(x *vigo.X, opts *createOpts) (*models.AppUser, error) {
appUser := &models.AppUser{
AppID: opts.AppID,
UserID: opts.UserID,

@ -7,13 +7,19 @@ import (
)
type deleteOpts struct {
AppID string `parse:"path@app_id"`
UserID string `parse:"path@user_id"`
AppID string `src:"path@app_id" desc:"应用ID"`
UserID string `src:"path@user_id" desc:"用户ID"`
}
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{}
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)
@ -23,9 +29,9 @@ func deleteAppUser(x *vigo.X, opts *deleteOpts) (any, error) {
return nil, err
}
return map[string]interface{}{
"message": "app_user deleted successfully",
"app_id": opts.AppID,
"user_id": opts.UserID,
return &deleteAppUserResponse{
Message: "app_user deleted successfully",
AppID: opts.AppID,
UserID: opts.UserID,
}, nil
}

@ -7,13 +7,13 @@ import (
)
type appUserIDReq struct {
AppID string `parse:"path@app_id"`
UserID string `parse:"path@user_id"`
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) (any, error) {
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 {

@ -1,16 +1,16 @@
package app_user
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type listOpts struct {
AppID string `parse:"path@app_id"`
Page int `parse:"query" default:"1"`
PageSize int `parse:"query" default:"20"`
Status string `parse:"query" default:""`
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 {
@ -20,7 +20,7 @@ type listResponse struct {
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)
if opts.Status != "" {

@ -1,20 +1,20 @@
package app_user
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type updateOpts struct {
AppID string `parse:"path@app_id"`
UserID string `parse:"path@user_id"`
Status *string `json:"status" parse:"json"`
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) (any, error) {
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)

@ -1,20 +1,20 @@
package resource
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type createOpts struct {
AppID string `json:"app_id" parse:"path@app_id"`
Name string `json:"name" parse:"json"`
Des string `json:"des" parse:"json"`
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) (any, error) {
func createResource(x *vigo.X, opts *createOpts) (*models.Resource, error) {
// 创建资源对象
resource := &models.Resource{
AppID: opts.AppID,

@ -1,18 +1,23 @@
package resource
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type deleteOpts struct {
ResourceID string `parse:"path@resource_id"`
ResourceID string `src:"path@resource_id" desc:"资源ID"`
}
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{}
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 map[string]interface{}{
"message": "resource deleted successfully",
"id": opts.ResourceID,
return &deleteResourceResponse{
Message: "resource deleted successfully",
ID: opts.ResourceID,
}, nil
}

@ -1,18 +1,18 @@
package resource
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type getIDReq struct {
ID string `parse:"path@resource_id"`
ID string `src:"path@resource_id" desc:"资源ID"`
}
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{}
err := cfg.DB().Where("id = ?", req.ID).First(resource).Error

@ -1,16 +1,16 @@
package resource
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type listOpts struct {
Page int `parse:"query" default:"1"`
PageSize int `parse:"query" default:"20"`
AppID string `parse:"query"`
Name string `parse:"query"`
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 {
@ -20,7 +20,7 @@ type listResponse struct {
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{})
if opts.AppID != "" {

@ -1,20 +1,20 @@
package resource
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type updateOpts struct {
ResourceID string `parse:"path@resource_id"`
Name *string `json:"name" parse:"json"`
Des *string `json:"des" parse:"json"`
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) (any, error) {
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 {

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

@ -1,18 +1,23 @@
package role
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
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)
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{}
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{}{
"message": "role deleted successfully",
"id": opts.RoleID,
return &deleteRoleResponse{
Message: "role deleted successfully",
ID: opts.RoleID,
}, nil
}

@ -1,18 +1,18 @@
package role
import (
"github.com/vyes-ai/vigo"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes-ai/vigo"
)
type getIDReq struct {
ID string `parse:"path@role_id"`
ID string `src:"path@role_id" desc:"角色ID"`
}
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{}
err := cfg.DB().Where("id = ?", req.ID).First(role).Error

@ -9,12 +9,12 @@ import (
var _ = Router.Get("/", "获取角色列表", listRoles)
type listOpts struct {
AppID string `parse:"path@app_id"`
Page int `parse:"query" default:"1"`
PageSize int `parse:"query" default:"20"`
Keyword string `parse:"query" default:""`
SortBy string `parse:"query" default:"created_at"`
Order string `parse:"query" default:"desc"`
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 {
@ -22,7 +22,7 @@ type listResponse struct {
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)
query := db

@ -7,14 +7,14 @@ import (
)
type updateOpts struct {
RoleID string `parse:"path@role_id"` // 角色 ID
Name *string `json:"name" parse:"json"` // 可选,角色名称
Des *string `json:"des" parse:"json"` // 可选,角色描述
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) (any, error) {
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 {

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

@ -15,10 +15,10 @@ import (
// SendCodeRequest 发送验证码请求
type SendCodeRequest struct {
Phone string `json:"phone" parse:"json"`
Region string `json:"region" parse:"json"`
Purpose string `json:"purpose" parse:"json"`
TemplateID string `json:"template_id" parse:"json"`
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 发送验证码响应
@ -29,10 +29,10 @@ type SendCodeResponse struct {
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 发送验证码
func sendCode(x *vigo.X, req *SendCodeRequest) (any, error) {
func sendCode(x *vigo.X, req *SendCodeRequest) (*SendCodeResponse, error) {
// 1. 验证手机号
normalizedPhone := utils.NormalizePhoneNumber(req.Phone)
if !utils.ValidatePhoneNumber(normalizedPhone) {

@ -9,14 +9,14 @@ import (
)
type patchOpts struct {
ID string `json:"id" parse:"path@token_id"`
ExpiredAt *time.Time `json:"expired_at" parse:"json"`
OverPerm *string `json:"over_perm" parse:"json"`
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) (any, error) {
func tokenPatch(x *vigo.X, opts *patchOpts) (*models.Token, error) {
data := &models.Token{}
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 {
ID string `parse:"path@token_id"`
ID string `src:"path@token_id" desc:"令牌ID"`
}
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{}
err := cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
return data, err

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

@ -14,26 +14,26 @@ import (
)
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)
func tokenGet(x *vigo.X, opts *getOpts) (any, error) {
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"`
UserID string `json:"user_id" gorm:"index;type:varchar(36)" parse:"query"`
AppID string `json:"app_id" gorm:"index;type:varchar(36)" parse:"query"`
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) (any, error) {
func tokenList(x *vigo.X, opts *listOpts) ([]*models.Token, error) {
data := make([]*models.Token, 0, 10)
query := cfg.DB()

@ -26,18 +26,18 @@ import (
var _ = Router.Post("/", "用户注册", vigo.SkipBefore, publicLimits, userPost)
type postOpts struct {
Username string `json:"username" gorm:"varchar(100);unique;default:not null" parse:"json"`
Code string `json:"code" gorm:"varchar(128)" parse:"json"`
Phone string `json:"phone" gorm:"varchar(50);unique;default:null" parse:"json"`
VerifyCode string `json:"verify_code" parse:"json"`
Region string `json:"region" parse:"json"`
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" parse:"json"`
Icon *string `json:"icon" parse:"json"`
Email *string `json:"email" gorm:"varchar(20);unique;default:null" parse:"json"`
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) (any, error) {
func userPost(x *vigo.X, opts *postOpts) (*models.User, error) {
data := &models.User{}
if cfg.Config.SMS.Enable {

@ -29,20 +29,20 @@ var _ = Router.Post("/login", "用户登录",
userLogin)
type loginOpts struct {
UserName string `json:"username" parse:"json"`
Code string `json:"code" parse:"json"`
UserName string `json:"username" src:"json" desc:"用户名"`
Code string `json:"code" src:"json" desc:"密码/授权码"`
VerifyCode string `json:"verify_code" parse:"json"`
Region string `json:"region" parse:"json"`
Phone string `json:"phone" parse:"json"`
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" parse:"json"`
Type *string `json:"type" parse:"json"`
AppID *string `json:"app_id" parse:"json"`
Device *string `json:"device" parse:"json"`
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) (any, error) {
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{}
@ -61,24 +61,24 @@ func userLogin(x *vigo.X, opts *loginOpts) (any, error) {
}
err := query.First(user).Error
if err != nil {
return nil, vigo.ErrNotFound
return "", vigo.ErrNotFound
}
logv.Info().Str("user", user.ID).Msg("login")
if opts.VerifyCode != "" {
err = sms.VerifyCode(opts.Phone, opts.VerifyCode, opts.Region, "signin")
if err != nil {
logv.Warn().Msgf("verify code: %v", err)
return nil, vigo.ErrNotAuthorized.WithError(err)
return "", vigo.ErrNotAuthorized.WithError(err)
}
} else {
code, err := base64.URLEncoding.DecodeString(opts.Code)
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))
if err != nil || string(ncode) != user.ID {
return nil, vigo.ErrNotAuthorized
return "", vigo.ErrNotAuthorized
}
}
aid := cfg.Config.ID
@ -97,22 +97,18 @@ func userLogin(x *vigo.X, opts *loginOpts) (any, error) {
err = cfg.DB().Create(data).Error
if err != nil {
return nil, err
return "", err
}
claim := &auth.Claims{}
claim.AID = aid
claim.UID = user.ID
claim.ID = data.ID
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
claim.UID = user.ID
claim.AID = aid
claim.Name = user.Username
if user.Nickname != "" {
claim.Name = user.Nickname
}
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 map[string]any{
"token": token,
"expired_at": data.ExpiredAt,
}, err
return auth.GenJwt(claim)
}

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

@ -14,12 +14,12 @@ import (
)
type deleteIDReq struct {
ID string `parse:"path@id"`
ID string `src:"path@id" desc:"记录ID"`
}
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{}
err := cfg.DB().Where("id = ?", req.ID).Delete(data).Error
return data, err

@ -15,34 +15,36 @@ import (
)
type getIDReq struct {
ID string `parse:"path@id"`
ID string `src:"path@id" desc:"记录ID"`
}
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{}
err := cfg.DB().Where("id = ?", req.ID).First(data).Error
return data, err
}
type listOpts struct {
UserID *string `json:"user_id" parse:"path@user_id"`
RoleID *string `json:"role_id" parse:"query"`
AppID *string `json:"app_id" parse:"query"`
Status *string `json:"status" parse:"query"`
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)
func userRoleList(x *vigo.X, opts *listOpts) (any, error) {
data := make([]*struct {
models.UserRole
Username string `json:"username"`
Nickname string `json:"nickname"`
Icon string `json:"icon"`
RoleName string `json:"role_name"`
}, 0, 10)
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").

@ -14,13 +14,13 @@ import (
)
type patchOpts struct {
ID string `json:"id" parse:"path@id"`
Status *string `json:"status" parse:"json"`
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) (any, error) {
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 {

@ -9,12 +9,12 @@ import (
)
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)
func userDelete(x *vigo.X, req *userIDReq) (any, error) {
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
@ -27,7 +27,7 @@ func checkOwner(x *vigo.X, data any) bool {
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{}
err := cfg.DB().Where("id = ?", req.UserID).First(data).Error
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)
type patchOpts struct {
ID string `json:"id" parse:"path@user_id"`
Nickname *string `json:"nickname" parse:"json"`
Icon *string `json:"icon" parse:"json"`
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" parse:"json"`
Email *string `json:"email" parse:"json"`
Phone *string `json:"phone" parse:"json"`
Status *uint `json:"status" parse:"json"`
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) (any, error) {
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 {

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

@ -29,13 +29,13 @@ var (
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) {
@ -60,7 +60,7 @@ func ParseJwt(tokenString string, keys ...string) (*Claims, error) {
return claims, nil
}
func checkJWT(x *vigo.X) (*Claims, error) {
func CheckJWT(x *vigo.X) (*Claims, error) {
authHeader := x.Request.Header.Get("Authorization")
if authHeader == "" {
authHeader = x.Request.URL.Query().Get("Authorization")
@ -82,10 +82,6 @@ func checkJWT(x *vigo.X) (*Claims, error) {
return claims, nil
}
func CheckJWT(x *vigo.X) (any, error) {
return checkJWT(x)
}
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) {
@ -93,7 +89,7 @@ func Check(target string, pid string, l AuthLevel, funcs ...CustomCheckFunc) fun
var err error
claims, ok := x.Get("token").(*Claims)
if !ok {
claims, err = checkJWT(x)
claims, err = CheckJWT(x)
if err != nil {
return nil, err
}

@ -13,27 +13,27 @@ const AUSTATUS_REJECT = "reject"
type App struct {
vigo.Model
Name string `json:"name" parse:"json"`
Icon string `json:"icon" parse:"json"`
Des string `json:"des" parse:"json"`
Typ string `json:"typ" gorm:"default:public" parse:"json"`
Status string `json:"status" gorm:"default:ok" parse:"json"`
InitRoleID *string `json:"init_role_id" gorm:"index;type:varchar(36);default: null" parse:"json"`
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" parse:"json"`
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" parse:"path"`
AppID string `json:"app_id" src:"path" desc:"应用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"`
Status string `json:"status" parse:"json"`
Status string `json:"status" src:"json" desc:"状态"`
}
func (m *AppUser) onOk(tx *gorm.DB) (err error) {
@ -70,37 +70,37 @@ func (m *AppUser) AfterUpdate(tx *gorm.DB) error {
type Resource struct {
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"`
Name string `json:"name" parse:"json"`
Des string `json:"des" parse:"json"`
Name string `json:"name" src:"json" desc:"名称"`
Des string `json:"des" src:"json" desc:"描述"`
}
type Role struct {
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"`
Name string `json:"name" parse:"json"`
Des string `json:"des" parse:"json"`
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)" 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"`
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"`
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"`
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"`
Name string `json:"name" parse:"json"`
TID string `json:"tid" parse:"json"`
Level uint `json:"level" parse:"json"`
Name string `json:"name" src:"json" desc:"名称"`
TID string `json:"tid" src:"json" desc:"资源ID"`
Level uint `json:"level" src:"json" desc:"级别"`
}

@ -10,7 +10,6 @@ import (
"strings"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/oauth"
"github.com/google/uuid"
"github.com/vyes-ai/vigo"
@ -21,21 +20,6 @@ var AllModels = &vigo.ModelList{}
func init() {
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 {
@ -88,5 +72,5 @@ func InitDB() error {
if app.InitRoleID == nil {
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
import (
"github.com/vyes-ai/vigo"
"time"
"github.com/vyes-ai/vigo"
)
// refresh token由oa 秘钥签发,有效期长, 存储在token表
@ -10,12 +11,12 @@ import (
// OverPerm 非oa应用获取oa数据的权限由用户设定
type Token struct {
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:"-"`
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:"-"`
ExpiredAt time.Time `json:"expired_at" parse:"json"`
OverPerm string `json:"over_perm" parse:"json"`
Device string `json:"device" parse:"json"`
ExpiredAt time.Time `json:"expired_at" src:"json" desc:"过期时间"`
OverPerm string `json:"over_perm" src:"json" desc:"覆盖权限"`
Device string `json:"device" src:"json" desc:"设备信息"`
Ip string `json:"ip"`
}

@ -10,29 +10,29 @@ import (
// code 64 hex / 32 byte / 256 bit
type User struct {
vigo.Model
Username string `json:"username" gorm:"type:varchar(100);unique;default:not null" parse:"json"`
Nickname string `json:"nickname" gorm:"type:varchar(100)" parse:"json"`
Icon string `json:"icon" 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)" src:"json" desc:"昵称"`
Icon string `json:"icon" src:"json" desc:"头像"`
Email string `json:"email" gorm:"unique;type:varchar(64);null;default:null" parse:"json"`
Phone string `json:"phone" gorm:"type:varchar(64);unique;null;default:null" parse:"json"`
Region string `json:"region" gorm:"type:varchar(32);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" src:"json" desc:"手机号"`
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)"`
Code string `json:"-" gorm:"type:varchar(64)" parse:"json"`
Code string `json:"-" gorm:"type:varchar(64)" src:"json" desc:"代码"`
}
type UserRole struct {
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"`
RoleID string `json:"role_id" parse:"json"`
RoleID string `json:"role_id" src:"json" desc:"角色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"`
Status string `json:"status"`

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

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

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

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

@ -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