fix service api (#556)

* fix service api

* update ServiceResponseItem

* fix: ddns lock
This commit is contained in:
UUBulb 2024-12-05 21:00:02 +08:00 committed by GitHub
parent 2234cff5eb
commit 040c8df02e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 124 additions and 53 deletions

View File

@ -60,7 +60,7 @@ func routers(r *gin.Engine, adminFrontend, userFrontend fs.FS) {
optionalAuth.GET("/ws/server", commonHandler(serverStream))
optionalAuth.GET("/server-group", commonHandler(listServerGroup))
optionalAuth.GET("/service", commonHandler(listService))
optionalAuth.GET("/service", commonHandler(showService))
optionalAuth.GET("/service/:id", commonHandler(listServiceHistory))
optionalAuth.GET("/service/server", commonHandler(listServerWithServices))
@ -82,6 +82,7 @@ func routers(r *gin.Engine, adminFrontend, userFrontend fs.FS) {
auth.POST("/user", commonHandler(createUser))
auth.POST("/batch-delete/user", commonHandler(batchDeleteUser))
auth.GET("/service/list", commonHandler(listService))
auth.POST("/service", commonHandler(createService))
auth.PATCH("/service/:id", commonHandler(updateService))
auth.POST("/batch-delete/service", commonHandler(batchDeleteService))

View File

@ -23,8 +23,8 @@ import (
func listDDNS(c *gin.Context) ([]*model.DDNSProfile, error) {
var ddnsProfiles []*model.DDNSProfile
singleton.DDNSCacheLock.RLock()
defer singleton.DDNSCacheLock.RUnlock()
singleton.DDNSListLock.RLock()
defer singleton.DDNSListLock.RUnlock()
if err := copier.Copy(&ddnsProfiles, &singleton.DDNSList); err != nil {
return nil, err

View File

@ -22,8 +22,8 @@ import (
func listNAT(c *gin.Context) ([]*model.NAT, error) {
var n []*model.NAT
singleton.NATCacheRwLock.RLock()
defer singleton.NATCacheRwLock.RUnlock()
singleton.NATListLock.RLock()
defer singleton.NATListLock.RUnlock()
if err := copier.Copy(&n, &singleton.NATList); err != nil {
return nil, err

View File

@ -20,8 +20,8 @@ import (
// @Success 200 {object} model.CommonResponse[[]model.Notification]
// @Router /notification [get]
func listNotification(c *gin.Context) ([]*model.Notification, error) {
singleton.NotificationsLock.RLock()
defer singleton.NotificationsLock.RUnlock()
singleton.NotificationSortedLock.RLock()
defer singleton.NotificationSortedLock.RUnlock()
var notifications []*model.Notification
if err := copier.Copy(&notifications, &singleton.NotificationListSorted); err != nil {

View File

@ -13,35 +13,22 @@ import (
"gorm.io/gorm"
)
// List service
// @Summary List service
// Show service
// @Summary Show service
// @Security BearerAuth
// @Schemes
// @Description List service
// @Description Show service
// @Tags common
// @Produce json
// @Success 200 {object} model.CommonResponse[model.ServiceResponse]
// @Router /service [get]
func listService(c *gin.Context) (*model.ServiceResponse, error) {
func showService(c *gin.Context) (*model.ServiceResponse, error) {
res, err, _ := requestGroup.Do("list-service", func() (interface{}, error) {
singleton.AlertsLock.RLock()
defer singleton.AlertsLock.RUnlock()
var stats map[uint64]model.ServiceResponseItem
stats := singleton.ServiceSentinelShared.CopyStats()
var cycleTransferStats map[uint64]model.CycleTransferStats
copier.Copy(&stats, singleton.ServiceSentinelShared.LoadStats())
copier.Copy(&cycleTransferStats, singleton.AlertsCycleTransferStatsStore)
_, isMember := c.Get(model.CtxKeyAuthorizedUser)
authorized := isMember // TODO || isViewPasswordVerfied
for k, service := range stats {
if !authorized {
if !service.Service.EnableShowInService {
delete(stats, k)
continue
}
service.Service = &model.Service{Name: service.Service.Name}
stats[k] = service
}
}
return []interface {
}{
stats, cycleTransferStats,
@ -57,6 +44,27 @@ func listService(c *gin.Context) (*model.ServiceResponse, error) {
}, nil
}
// List service
// @Summary List service
// @Security BearerAuth
// @Schemes
// @Description List service
// @Tags auth required
// @Produce json
// @Success 200 {object} model.CommonResponse[[]model.Service]
// @Router /service [get]
func listService(c *gin.Context) ([]*model.Service, error) {
singleton.ServiceSentinelShared.ServicesLock.RLock()
defer singleton.ServiceSentinelShared.ServicesLock.RUnlock()
var ss []*model.Service
if err := copier.Copy(&ss, singleton.ServiceSentinelShared.ServiceList); err != nil {
return nil, err
}
return ss, nil
}
// List service histories by server id
// @Summary List service histories by server id
// @Security BearerAuth
@ -218,7 +226,12 @@ func createService(c *gin.Context) (uint64, error) {
return 0, err
}
return m.ID, singleton.ServiceSentinelShared.OnServiceUpdate(m)
if err := singleton.ServiceSentinelShared.OnServiceUpdate(m); err != nil {
return 0, err
}
singleton.ServiceSentinelShared.UpdateServiceList()
return m.ID, nil
}
// Update service
@ -281,7 +294,12 @@ func updateService(c *gin.Context) (any, error) {
return nil, err
}
return nil, singleton.ServiceSentinelShared.OnServiceUpdate(m)
if err := singleton.ServiceSentinelShared.OnServiceUpdate(m); err != nil {
return nil, err
}
singleton.ServiceSentinelShared.UpdateServiceList()
return nil, nil
}
// Batch delete service
@ -310,5 +328,6 @@ func batchDeleteService(c *gin.Context) (any, error) {
return nil, err
}
singleton.ServiceSentinelShared.OnServiceDelete(ids)
singleton.ServiceSentinelShared.UpdateServiceList()
return nil, nil
}

View File

@ -21,7 +21,7 @@ type ServiceForm struct {
}
type ServiceResponseItem struct {
Service *Service `json:"service,omitempty"`
ServiceName string `json:"service_name,omitempty"`
CurrentUp uint64 `json:"current_up"`
CurrentDown uint64 `json:"current_down"`
TotalUp uint64 `json:"total_up"`

View File

@ -19,6 +19,7 @@ var (
DDNSCache map[uint64]*model.DDNSProfile
DDNSCacheLock sync.RWMutex
DDNSList []*model.DDNSProfile
DDNSListLock sync.RWMutex
)
func initDDNS() {
@ -52,6 +53,9 @@ func UpdateDDNSList() {
DDNSCacheLock.RLock()
defer DDNSCacheLock.RUnlock()
DDNSListLock.Lock()
defer DDNSListLock.Unlock()
DDNSList = make([]*model.DDNSProfile, 0, len(DDNSCache))
for _, p := range DDNSCache {
DDNSList = append(DDNSList, p)

View File

@ -14,6 +14,7 @@ var (
NATIDToDomain = make(map[uint64]string)
NATList []*model.NAT
NATListLock sync.RWMutex
)
func initNAT() {
@ -55,6 +56,9 @@ func UpdateNATList() {
NATCacheRwLock.RLock()
defer NATCacheRwLock.RUnlock()
NATListLock.Lock()
defer NATListLock.Unlock()
NATList = make([]*model.NAT, 0, len(NATCache))
for _, n := range NATCache {
NATList = append(NATList, n)

View File

@ -25,6 +25,7 @@ var (
NotificationGroup map[uint64]string // [NotificationGroupID] -> [NotificationGroupName]
NotificationsLock sync.RWMutex
NotificationSortedLock sync.RWMutex
NotificationGroupLock sync.RWMutex
)
@ -81,6 +82,9 @@ func UpdateNotificationList() {
NotificationsLock.RLock()
defer NotificationsLock.RUnlock()
NotificationSortedLock.Lock()
defer NotificationSortedLock.Unlock()
NotificationListSorted = make([]*model.Notification, 0, len(NotificationMap))
for _, n := range NotificationMap {
NotificationListSorted = append(NotificationListSorted, n)

View File

@ -3,12 +3,14 @@ package singleton
import (
"fmt"
"log"
"sort"
"slices"
"strings"
"sync"
"time"
"github.com/jinzhu/copier"
"github.com/nezhahq/nezha/model"
"github.com/nezhahq/nezha/pkg/utils"
pb "github.com/nezhahq/nezha/proto"
)
@ -18,6 +20,12 @@ const (
var ServiceSentinelShared *ServiceSentinel
type serviceResponseItem struct {
model.ServiceResponseItem
service *model.Service
}
type ReportData struct {
Data *pb.TaskResult
Reporter uint64
@ -45,7 +53,7 @@ func NewServiceSentinel(serviceSentinelDispatchBus chan<- model.Service) {
Services: make(map[uint64]*model.Service),
tlsCertCache: make(map[uint64]string),
// 30天数据缓存
monthlyStatus: make(map[uint64]*model.ServiceResponseItem),
monthlyStatus: make(map[uint64]*serviceResponseItem),
dispatchBus: serviceSentinelDispatchBus,
}
// 加载历史记录
@ -105,11 +113,13 @@ type ServiceSentinel struct {
tlsCertCache map[uint64]string
ServicesLock sync.RWMutex
ServiceListLock sync.RWMutex
Services map[uint64]*model.Service
ServiceList []*model.Service
// 30天数据缓存
monthlyStatusLock sync.Mutex
monthlyStatus map[uint64]*model.ServiceResponseItem
monthlyStatus map[uint64]*serviceResponseItem
}
type indexStore struct {
@ -157,17 +167,21 @@ func (ss *ServiceSentinel) Dispatch(r ReportData) {
ss.serviceReportChannel <- r
}
func (ss *ServiceSentinel) GetServiceList() []*model.Service {
func (ss *ServiceSentinel) UpdateServiceList() {
ss.ServicesLock.RLock()
defer ss.ServicesLock.RUnlock()
var services []*model.Service
ss.ServiceListLock.Lock()
defer ss.ServiceListLock.Unlock()
ss.ServiceList = make([]*model.Service, 0, len(ss.Services))
for _, v := range ss.Services {
services = append(services, v)
ss.ServiceList = append(ss.ServiceList, v)
}
sort.SliceStable(services, func(i, j int) bool {
return services[i].ID < services[j].ID
slices.SortFunc(ss.ServiceList, func(a, b *model.Service) int {
return utils.Compare(a.ID, b.ID)
})
return services
}
// loadServiceHistory 加载服务监控器的历史状态信息
@ -198,16 +212,19 @@ func (ss *ServiceSentinel) loadServiceHistory() {
ss.serviceCurrentStatusData[services[i].ID] = make([]*pb.TaskResult, _CurrentStatusSize)
ss.serviceStatusToday[services[i].ID] = &_TodayStatsOfService{}
}
ss.ServiceList = services
year, month, day := time.Now().Date()
today := time.Date(year, month, day, 0, 0, 0, 0, Loc)
for i := 0; i < len(services); i++ {
ServiceSentinelShared.monthlyStatus[services[i].ID] = &model.ServiceResponseItem{
Service: services[i],
ServiceSentinelShared.monthlyStatus[services[i].ID] = &serviceResponseItem{
service: services[i],
ServiceResponseItem: model.ServiceResponseItem{
Delay: &[30]float32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Up: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Down: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
}
}
@ -250,11 +267,13 @@ func (ss *ServiceSentinel) OnServiceUpdate(m model.Service) error {
Cron.Remove(ss.Services[m.ID].CronJobID)
} else {
// 新任务初始化数据
ss.monthlyStatus[m.ID] = &model.ServiceResponseItem{
Service: &m,
ss.monthlyStatus[m.ID] = &serviceResponseItem{
service: &m,
ServiceResponseItem: model.ServiceResponseItem{
Delay: &[30]float32{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Up: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Down: &[30]int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
},
}
ss.serviceCurrentStatusData[m.ID] = make([]*pb.TaskResult, _CurrentStatusSize)
ss.serviceStatusToday[m.ID] = &_TodayStatsOfService{}
@ -290,7 +309,9 @@ func (ss *ServiceSentinel) OnServiceDelete(ids []uint64) {
}
}
func (ss *ServiceSentinel) LoadStats() map[uint64]*model.ServiceResponseItem {
func (ss *ServiceSentinel) LoadStats() map[uint64]*serviceResponseItem {
ss.ServicesLock.RLock()
defer ss.ServicesLock.RUnlock()
ss.serviceResponseDataStoreLock.RLock()
defer ss.serviceResponseDataStoreLock.RUnlock()
ss.monthlyStatusLock.Lock()
@ -298,7 +319,7 @@ func (ss *ServiceSentinel) LoadStats() map[uint64]*model.ServiceResponseItem {
// 刷新最新一天的数据
for k := range ss.Services {
ss.monthlyStatus[k].Service = ss.Services[k]
ss.monthlyStatus[k].service = ss.Services[k]
v := ss.serviceStatusToday[k]
// 30 天在线率,
@ -325,6 +346,24 @@ func (ss *ServiceSentinel) LoadStats() map[uint64]*model.ServiceResponseItem {
return ss.monthlyStatus
}
func (ss *ServiceSentinel) CopyStats() map[uint64]model.ServiceResponseItem {
var stats map[uint64]*serviceResponseItem
copier.Copy(&stats, ss.LoadStats())
sri := make(map[uint64]model.ServiceResponseItem)
for k, service := range stats {
if !service.service.EnableShowInService {
delete(stats, k)
continue
}
service.ServiceName = service.service.Name
sri[k] = service.ServiceResponseItem
}
return sri
}
// worker 服务监控的实际工作流程
func (ss *ServiceSentinel) worker() {
// 从服务状态汇报管道获取汇报的服务数据