From 76928b71d9d90180fa1448d82753dc9f980ecc3f Mon Sep 17 00:00:00 2001 From: Akkia Date: Tue, 17 May 2022 11:21:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9C=8D=E5=8A=A1=E5=99=A8=E7=8A=B6?= =?UTF-8?q?=E6=80=81API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/dashboard/controller/member_api.go | 59 ++++++++++++++++++ model/api_token.go | 10 ++++ pkg/mygin/auth.go | 14 ++++- service/singleton/api.go | 83 ++++++++++++++++++++++++++ service/singleton/server.go | 9 ++- service/singleton/singleton.go | 2 +- 6 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 model/api_token.go create mode 100644 service/singleton/api.go diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index 1b4f2c8..904ef33 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -43,6 +43,47 @@ func (ma *memberAPI) serve() { mr.POST("/setting", ma.updateSetting) mr.DELETE("/:model/:id", ma.delete) mr.POST("/logout", ma.logout) + + // API + mr.GET("/server/list", ma.serverList) + mr.GET("/server/details", ma.serverDetails) +} + +// serverList 获取服务器列表 +func (ma *memberAPI) serverList(c *gin.Context) { + +} + +// serverDetails 获取服务器信息 +// header: Authorization: Token +// query: idList (服务器ID,逗号分隔,优先级高于tag查询) +// query: tag (服务器分组) +func (ma *memberAPI) serverDetails(c *gin.Context) { + token, _ := c.Cookie("Authorization") + var idList []uint64 + idListStr := strings.Split(c.Query("id"), ",") + if c.Query("id") != "" { + idList = make([]uint64, len(idListStr)) + for i, v := range idListStr { + id, _ := strconv.ParseUint(v, 10, 64) + idList[i] = id + } + } + tag := c.Query("tag") + serverAPI := &singleton.ServerAPI{ + Token: token, + IDList: idList, + Tag: tag, + } + if tag != "" { + c.JSON(200, serverAPI.GetStatusByTag()) + return + } + if len(idList) != 0 { + c.JSON(200, serverAPI.GetStatusByIDList()) + return + } + c.JSON(200, serverAPI.GetAllStatus()) } func (ma *memberAPI) delete(c *gin.Context) { @@ -194,6 +235,23 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) { // 设置新的 Secret-ID 绑定关系 delete(singleton.SecretToID, singleton.ServerList[s.ID].Secret) } + // 如果修改了Tag + if s.Tag != singleton.ServerList[s.ID].Tag { + index := 0 + for index < len(singleton.ServerTagToIDList[s.Tag]) { + if singleton.ServerTagToIDList[s.Tag][index] == s.ID { + break + } + index++ + } + // 删除旧 Tag-ID 绑定关系 + singleton.ServerTagToIDList[singleton.ServerList[s.ID].Tag] = append(singleton.ServerTagToIDList[singleton.ServerList[s.ID].Tag][:index], singleton.ServerTagToIDList[singleton.ServerList[s.ID].Tag][index+1:]...) + // 设置新的 Tag-ID 绑定关系 + singleton.ServerTagToIDList[s.Tag] = append(singleton.ServerTagToIDList[s.Tag], s.ID) + if len(singleton.ServerTagToIDList[s.Tag]) == 0 { + delete(singleton.ServerTagToIDList, s.Tag) + } + } singleton.ServerList[s.ID] = &s singleton.ServerLock.Unlock() } else { @@ -202,6 +260,7 @@ func (ma *memberAPI) addOrEditServer(c *gin.Context) { singleton.ServerLock.Lock() singleton.SecretToID[s.Secret] = s.ID singleton.ServerList[s.ID] = &s + singleton.ServerTagToIDList[s.Tag] = append(singleton.ServerTagToIDList[s.Tag], s.ID) singleton.ServerLock.Unlock() } singleton.ReSortServer() diff --git a/model/api_token.go b/model/api_token.go new file mode 100644 index 0000000..b07a571 --- /dev/null +++ b/model/api_token.go @@ -0,0 +1,10 @@ +package model + +import "time" + +type ApiToken struct { + Common + UserId uint64 `json:"user_id"` + Token string `json:"token"` + TokenExpired time.Time `json:"token_expired"` +} diff --git a/pkg/mygin/auth.go b/pkg/mygin/auth.go index 38123f8..9dcdfa1 100644 --- a/pkg/mygin/auth.go +++ b/pkg/mygin/auth.go @@ -34,7 +34,6 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) { Link: opt.Redirect, Btn: opt.Btn, } - var isLogin bool // 用户鉴权 @@ -50,6 +49,19 @@ func Authorize(opt AuthorizeOption) func(*gin.Context) { } } + // API鉴权 + apiToken := c.GetHeader("Authorization") + if apiToken != "" { + var t model.ApiToken + // TODO: 需要有缓存机制 减少数据库查询次数 + if err := singleton.DB.Where("token = ?", apiToken).First(&t).Error; err == nil { + isLogin = t.TokenExpired.After(time.Now()) + } + if isLogin { + c.Set(model.CtxKeyAuthorizedUser, &t) + } + } + // 已登录且只能游客访问 if isLogin && opt.Guest { ShowErrorPage(c, commonErr, opt.IsPage) diff --git a/service/singleton/api.go b/service/singleton/api.go new file mode 100644 index 0000000..b7fa1a6 --- /dev/null +++ b/service/singleton/api.go @@ -0,0 +1,83 @@ +package singleton + +import "github.com/naiba/nezha/model" + +type ServerAPI struct { + Token string // 传入Token 后期可能会需要用于scope判定 + IDList []uint64 + Tag string +} + +type CommonResponse struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type StatusResponse struct { + Host *model.Host `json:"host"` + Status *model.HostState `json:"status"` +} + +type ServerStatusResponse struct { + CommonResponse + Result []*StatusResponse `json:"result"` +} + +// GetStatusByIDList 获取传入IDList的服务器状态信息 +func (s *ServerAPI) GetStatusByIDList() *ServerStatusResponse { + var res []*StatusResponse + + ServerLock.RLock() + defer ServerLock.RUnlock() + + for _, v := range s.IDList { + server := ServerList[v] + if server == nil { + continue + } + res = append(res, &StatusResponse{ + Host: server.Host, + Status: server.State, + }) + } + + return &ServerStatusResponse{ + CommonResponse: CommonResponse{ + Code: 0, + Message: "success", + }, + Result: res, + } +} + +// GetStatusByTag 获取传入分组的所有服务器状态信息 +func (s *ServerAPI) GetStatusByTag() *ServerStatusResponse { + s.IDList = ServerTagToIDList[s.Tag] + return s.GetStatusByIDList() +} + +// GetAllStatus 获取所有服务器状态信息 +func (s *ServerAPI) GetAllStatus() *ServerStatusResponse { + ServerLock.RLock() + defer ServerLock.RUnlock() + var res []*StatusResponse + for _, v := range ServerList { + host := v.Host + state := v.State + if host == nil || state == nil { + continue + } + res = append(res, &StatusResponse{ + Host: v.Host, + Status: v.State, + }) + } + + return &ServerStatusResponse{ + CommonResponse: CommonResponse{ + Code: 0, + Message: "success", + }, + Result: res, + } +} diff --git a/service/singleton/server.go b/service/singleton/server.go index 9dfcf72..31ad29b 100644 --- a/service/singleton/server.go +++ b/service/singleton/server.go @@ -8,9 +8,10 @@ import ( ) var ( - ServerList map[uint64]*model.Server // [ServerID] -> model.Server - SecretToID map[string]uint64 // [ServerSecret] -> ServerID - ServerLock sync.RWMutex + ServerList map[uint64]*model.Server // [ServerID] -> model.Server + SecretToID map[string]uint64 // [ServerSecret] -> ServerID + ServerTagToIDList map[string][]uint64 // [ServerTag] -> ServerID + ServerLock sync.RWMutex SortedServerList []*model.Server // 用于存储服务器列表的 slice,按照服务器 ID 排序 SortedServerLock sync.RWMutex @@ -20,6 +21,7 @@ var ( func InitServer() { ServerList = make(map[uint64]*model.Server) SecretToID = make(map[string]uint64) + ServerTagToIDList = make(map[string][]uint64) } //LoadServers 加载服务器列表并根据ID排序 @@ -33,6 +35,7 @@ func LoadServers() { innerS.State = &model.HostState{} ServerList[innerS.ID] = &innerS SecretToID[innerS.Secret] = innerS.ID + ServerTagToIDList[innerS.Tag] = append(ServerTagToIDList[innerS.Tag], innerS.ID) } ReSortServer() } diff --git a/service/singleton/singleton.go b/service/singleton/singleton.go index 7a3f1e5..e66e01c 100644 --- a/service/singleton/singleton.go +++ b/service/singleton/singleton.go @@ -63,7 +63,7 @@ func InitDBFromPath(path string) { } err = DB.AutoMigrate(model.Server{}, model.User{}, model.Notification{}, model.AlertRule{}, model.Monitor{}, - model.MonitorHistory{}, model.Cron{}, model.Transfer{}) + model.MonitorHistory{}, model.Cron{}, model.Transfer{}, model.ApiToken{}) if err != nil { panic(err) }