From a74ccb104fc5f5ee23cc33df5e1bdbd1a7a0744d Mon Sep 17 00:00:00 2001 From: veypi Date: Mon, 2 Feb 2026 10:14:27 +0800 Subject: [PATCH] update to new vigo version --- agents.md | 21 ++ api/app/access/create.go | 16 +- api/app/access/del.go | 12 +- api/app/access/get.go | 4 +- api/app/access/list.go | 14 +- api/app/access/patch.go | 10 +- api/app/app.go | 57 ++-- api/app/app_user/create.go | 8 +- api/app/app_user/del.go | 20 +- api/app/app_user/get.go | 6 +- api/app/app_user/list.go | 12 +- api/app/app_user/patch.go | 10 +- api/app/resource/create.go | 10 +- api/app/resource/del.go | 17 +- api/app/resource/get.go | 6 +- api/app/resource/list.go | 12 +- api/app/resource/patch.go | 10 +- api/app/role/create.go | 8 +- api/app/role/del.go | 17 +- api/app/role/get.go | 6 +- api/app/role/list.go | 14 +- api/app/role/patch.go | 8 +- api/init.go | 17 +- api/sms/send.go | 12 +- api/token/base.go | 12 +- api/token/create.go | 28 +- api/token/get.go | 12 +- api/user/create.go | 18 +- api/user/login.go | 48 ++-- api/user/role/create.go | 10 +- api/user/role/del.go | 4 +- api/user/role/get.go | 30 ++- api/user/role/patch.go | 6 +- api/user/user.go | 22 +- init.go | 2 +- libs/auth/jwt.go | 10 +- models/app.go | 46 ++-- models/init.go | 18 +- models/token.go | 13 +- models/user.go | 22 +- oauth/authorize.go | 19 +- oauth/revoke.go | 10 +- oauth/token.go | 87 +++--- ui/assets/common.css | 529 +++++++++++++------------------------ ui/assets/others.css | 82 ++++++ 45 files changed, 653 insertions(+), 702 deletions(-) create mode 100644 agents.md create mode 100644 ui/assets/others.css diff --git a/agents.md b/agents.md new file mode 100644 index 0000000..4f6f794 --- /dev/null +++ b/agents.md @@ -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 diff --git a/api/app/access/create.go b/api/app/access/create.go index 8eacd72..b0dc995 100644 --- a/api/app/access/create.go +++ b/api/app/access/create.go @@ -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, diff --git a/api/app/access/del.go b/api/app/access/del.go index 68b7968..05d714b 100644 --- a/api/app/access/del.go +++ b/api/app/access/del.go @@ -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 } diff --git a/api/app/access/get.go b/api/app/access/get.go index f3e88d7..720e6e4 100644 --- a/api/app/access/get.go +++ b/api/app/access/get.go @@ -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 diff --git a/api/app/access/list.go b/api/app/access/list.go index 30401e5..99dc46d 100644 --- a/api/app/access/list.go +++ b/api/app/access/list.go @@ -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 != "" { diff --git a/api/app/access/patch.go b/api/app/access/patch.go index 0744f86..db93c76 100644 --- a/api/app/access/patch.go +++ b/api/app/access/patch.go @@ -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 { diff --git a/api/app/app.go b/api/app/app.go index c2c648e..27c0160 100644 --- a/api/app/app.go +++ b/api/app/app.go @@ -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 diff --git a/api/app/app_user/create.go b/api/app/app_user/create.go index 7eddd20..7d39749 100644 --- a/api/app/app_user/create.go +++ b/api/app/app_user/create.go @@ -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, diff --git a/api/app/app_user/del.go b/api/app/app_user/del.go index eaeab3f..38db3cf 100644 --- a/api/app/app_user/del.go +++ b/api/app/app_user/del.go @@ -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 } diff --git a/api/app/app_user/get.go b/api/app/app_user/get.go index 8a2c019..fc2e476 100644 --- a/api/app/app_user/get.go +++ b/api/app/app_user/get.go @@ -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 { diff --git a/api/app/app_user/list.go b/api/app/app_user/list.go index a08c00d..ab68b6d 100644 --- a/api/app/app_user/list.go +++ b/api/app/app_user/list.go @@ -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 != "" { diff --git a/api/app/app_user/patch.go b/api/app/app_user/patch.go index a10b221..164ee33 100644 --- a/api/app/app_user/patch.go +++ b/api/app/app_user/patch.go @@ -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) diff --git a/api/app/resource/create.go b/api/app/resource/create.go index ea1572d..6223932 100644 --- a/api/app/resource/create.go +++ b/api/app/resource/create.go @@ -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, diff --git a/api/app/resource/del.go b/api/app/resource/del.go index ef595f4..67ba8d0 100644 --- a/api/app/resource/del.go +++ b/api/app/resource/del.go @@ -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 } diff --git a/api/app/resource/get.go b/api/app/resource/get.go index 6bcdcdb..6358b5a 100644 --- a/api/app/resource/get.go +++ b/api/app/resource/get.go @@ -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 diff --git a/api/app/resource/list.go b/api/app/resource/list.go index 3237b6c..380d70e 100644 --- a/api/app/resource/list.go +++ b/api/app/resource/list.go @@ -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 != "" { diff --git a/api/app/resource/patch.go b/api/app/resource/patch.go index 891fd77..ab79103 100644 --- a/api/app/resource/patch.go +++ b/api/app/resource/patch.go @@ -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 { diff --git a/api/app/role/create.go b/api/app/role/create.go index 1ba37fa..74d6a20 100644 --- a/api/app/role/create.go +++ b/api/app/role/create.go @@ -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, diff --git a/api/app/role/del.go b/api/app/role/del.go index 7a67a45..325c38f 100644 --- a/api/app/role/del.go +++ b/api/app/role/del.go @@ -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 } diff --git a/api/app/role/get.go b/api/app/role/get.go index 6325d1c..df8a347 100644 --- a/api/app/role/get.go +++ b/api/app/role/get.go @@ -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 diff --git a/api/app/role/list.go b/api/app/role/list.go index fb36f13..0bec309 100644 --- a/api/app/role/list.go +++ b/api/app/role/list.go @@ -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 diff --git a/api/app/role/patch.go b/api/app/role/patch.go index 6b10282..f84ac72 100644 --- a/api/app/role/patch.go +++ b/api/app/role/patch.go @@ -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 { diff --git a/api/init.go b/api/init.go index 465fa98..bf235dc 100644 --- a/api/init.go +++ b/api/init.go @@ -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, } } diff --git a/api/sms/send.go b/api/sms/send.go index 0315b46..068a2de 100644 --- a/api/sms/send.go +++ b/api/sms/send.go @@ -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) { diff --git a/api/token/base.go b/api/token/base.go index b184812..378f956 100644 --- a/api/token/base.go +++ b/api/token/base.go @@ -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 diff --git a/api/token/create.go b/api/token/create.go index 7ed1148..b3a19ba 100644 --- a/api/token/create.go +++ b/api/token/create.go @@ -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 } } diff --git a/api/token/get.go b/api/token/get.go index be4c8de..597c1b2 100644 --- a/api/token/get.go +++ b/api/token/get.go @@ -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() diff --git a/api/user/create.go b/api/user/create.go index 1a4d21c..e91e087 100644 --- a/api/user/create.go +++ b/api/user/create.go @@ -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 { diff --git a/api/user/login.go b/api/user/login.go index 6b7b729..4173c7e 100644 --- a/api/user/login.go +++ b/api/user/login.go @@ -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) } diff --git a/api/user/role/create.go b/api/user/role/create.go index 4e760cc..b7e2bd7 100644 --- a/api/user/role/create.go +++ b/api/user/role/create.go @@ -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 diff --git a/api/user/role/del.go b/api/user/role/del.go index f918223..49834fd 100644 --- a/api/user/role/del.go +++ b/api/user/role/del.go @@ -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 diff --git a/api/user/role/get.go b/api/user/role/get.go index 75131f5..c5d3486 100644 --- a/api/user/role/get.go +++ b/api/user/role/get.go @@ -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"). diff --git a/api/user/role/patch.go b/api/user/role/patch.go index 3b81713..beb4b3e 100644 --- a/api/user/role/patch.go +++ b/api/user/role/patch.go @@ -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 { diff --git a/api/user/user.go b/api/user/user.go index f593105..2b35e23 100644 --- a/api/user/user.go +++ b/api/user/user.go @@ -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 { diff --git a/init.go b/init.go index fcdb1ef..d1b0e43 100644 --- a/init.go +++ b/init.go @@ -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) } diff --git a/libs/auth/jwt.go b/libs/auth/jwt.go index ecdc6f7..684c36a 100644 --- a/libs/auth/jwt.go +++ b/libs/auth/jwt.go @@ -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 } diff --git a/models/app.go b/models/app.go index 19999fa..3aabdb6 100644 --- a/models/app.go +++ b/models/app.go @@ -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:"级别"` } diff --git a/models/init.go b/models/init.go index 74d3613..19d7883 100644 --- a/models/init.go +++ b/models/init.go @@ -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 } diff --git a/models/token.go b/models/token.go index 7f98eaa..d3affa5 100644 --- a/models/token.go +++ b/models/token.go @@ -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"` } diff --git a/models/user.go b/models/user.go index 11a2084..be288cd 100644 --- a/models/user.go +++ b/models/user.go @@ -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"` diff --git a/oauth/authorize.go b/oauth/authorize.go index 4042051..e98ff01 100644 --- a/oauth/authorize.go +++ b/oauth/authorize.go @@ -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. 验证响应类型 diff --git a/oauth/revoke.go b/oauth/revoke.go index 4b48b31..5f6cb16 100644 --- a/oauth/revoke.go +++ b/oauth/revoke.go @@ -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) } diff --git a/oauth/token.go b/oauth/token.go index fa3f1fb..2976b48 100644 --- a/oauth/token.go +++ b/oauth/token.go @@ -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 } // 辅助函数 diff --git a/ui/assets/common.css b/ui/assets/common.css index ad99145..eb00086 100644 --- a/ui/assets/common.css +++ b/ui/assets/common.css @@ -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; +} diff --git a/ui/assets/others.css b/ui/assets/others.css new file mode 100644 index 0000000..31fb7bf --- /dev/null +++ b/ui/assets/others.css @@ -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); +}