diff --git a/controller/monitor/iml.go b/controller/monitor/iml.go index 26cdbb9e..f1b26ada 100644 --- a/controller/monitor/iml.go +++ b/controller/monitor/iml.go @@ -17,6 +17,70 @@ type imlMonitorStatisticController struct { module monitor.IMonitorStatisticModule `autowired:""` } +func (i *imlMonitorStatisticController) Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error) { + switch dataType { + case monitor_dto.DataTypeApi: + return i.module.ApiStatistics(ctx, input) + case monitor_dto.DataTypeProvider: + return i.module.ProviderStatistics(ctx, input) + case monitor_dto.DataTypeSubscriber: + return i.module.SubscriberStatistics(ctx, input) + default: + return nil, fmt.Errorf("unsupported data type: %s", dataType) + } +} + +func (i *imlMonitorStatisticController) InvokeTrend(ctx *gin.Context, dataType string, id string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) { + switch dataType { + case monitor_dto.DataTypeApi: + return i.module.APITrend(ctx, id, input) + case monitor_dto.DataTypeProvider: + return i.module.ProviderTrend(ctx, id, input) + case monitor_dto.DataTypeSubscriber: + return i.module.SubscriberTrend(ctx, id, input) + default: + return nil, "", fmt.Errorf("unsupported data type: %s", dataType) + } +} + +func (i *imlMonitorStatisticController) InvokeTrendInner(ctx *gin.Context, dataType string, typ string, api string, provider string, subscriber string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) { + if dataType == monitor_dto.DataTypeApi && typ == monitor_dto.DataTypeSubscriber || dataType == monitor_dto.DataTypeSubscriber && typ == monitor_dto.DataTypeApi { + return i.module.InvokeTrendWithSubscriberAndApi(ctx, api, subscriber, input) + } else if dataType == monitor_dto.DataTypeApi && typ == monitor_dto.DataTypeProvider || dataType == monitor_dto.DataTypeProvider && typ == monitor_dto.DataTypeApi { + return i.module.InvokeTrendWithProviderAndApi(ctx, provider, api, input) + } + return nil, "", fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType) +} + +func (i *imlMonitorStatisticController) StatisticsInner(ctx *gin.Context, dataType string, typ string, id string, input *monitor_dto.StatisticInput) (interface{}, error) { + switch dataType { + case monitor_dto.DataTypeApi: + switch typ { + case monitor_dto.DataTypeProvider: + return i.module.ProviderStatisticsOnApi(ctx, id, input) + case monitor_dto.DataTypeSubscriber: + return i.module.SubscriberStatisticsOnApi(ctx, id, input) + default: + return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType) + } + case monitor_dto.DataTypeProvider: + switch typ { + case monitor_dto.DataTypeApi: + return i.module.ApiStatisticsOnProvider(ctx, id, input) + default: + return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType) + } + case monitor_dto.DataTypeSubscriber: + switch typ { + case monitor_dto.DataTypeApi: + return i.module.ApiStatisticsOnSubscriber(ctx, id, input) + default: + return nil, fmt.Errorf("unsupported detail type: %s, data type is %s", typ, dataType) + } + } + return nil, fmt.Errorf("unsupported data type: %s", dataType) +} + func (i *imlMonitorStatisticController) OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error) { trend, timeInterval, err := i.module.MessageTrend(ctx, input) if err != nil { diff --git a/controller/monitor/statistic.go b/controller/monitor/statistic.go index 33624b87..ca991f68 100644 --- a/controller/monitor/statistic.go +++ b/controller/monitor/statistic.go @@ -16,7 +16,12 @@ type IMonitorStatisticController interface { OverviewInvokeTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []int64, []int64, []int64, []int64, []float64, []float64, string, error) OverviewMessageTrend(ctx *gin.Context, input *monitor_dto.CommonInput) ([]time.Time, []float64, []float64, string, error) - //Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error) + Statistics(ctx *gin.Context, dataType string, input *monitor_dto.StatisticInput) (interface{}, error) + + InvokeTrend(ctx *gin.Context, dataType string, id string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) + + InvokeTrendInner(ctx *gin.Context, dataType string, typ string, api string, provider string, subscriber string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) + StatisticsInner(ctx *gin.Context, dataType string, typ string, id string, input *monitor_dto.StatisticInput) (interface{}, error) } type IMonitorConfigController interface { diff --git a/controller/service/iml.go b/controller/service/iml.go index 0c6bcbb9..1a5e024f 100644 --- a/controller/service/iml.go +++ b/controller/service/iml.go @@ -7,6 +7,8 @@ import ( "strings" "time" + upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto" + "github.com/eolinker/eosc/log" application_authorization "github.com/APIParkLab/APIPark/module/application-authorization" @@ -25,7 +27,6 @@ import ( "github.com/APIParkLab/APIPark/module/service" service_dto "github.com/APIParkLab/APIPark/module/service/dto" "github.com/APIParkLab/APIPark/module/upstream" - upstream_dto "github.com/APIParkLab/APIPark/module/upstream/dto" "github.com/eolinker/go-common/store" "github.com/gin-gonic/gin" "github.com/google/uuid" @@ -47,24 +48,12 @@ type imlServiceController struct { transaction store.ITransaction `autowired:""` } -func newAIUpstream(id string, provider string, uri model_runtime.IProviderURI) *upstream_dto.Upstream { - return &upstream_dto.Upstream{ - Type: "http", - Balance: "round-robin", - Timeout: 300000, - Retry: 0, - Remark: fmt.Sprintf("auto create by ai service %s,provider is %s", id, provider), - LimitPeerSecond: 0, - ProxyHeaders: nil, - Scheme: uri.Scheme(), - PassHost: "node", - Nodes: []*upstream_dto.NodeConfig{ - { - Address: uri.Host(), - Weight: 100, - }, - }, - } +func (i *imlServiceController) Simple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) { + return i.module.Simple(ctx) +} + +func (i *imlServiceController) MySimple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) { + return i.module.MySimple(ctx) } func (i *imlServiceController) editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) { @@ -228,21 +217,12 @@ func (i *imlServiceController) SearchMyServices(ctx *gin.Context, teamId string, return i.module.SearchMyServices(ctx, teamId, keyword) } -//func (i *imlServiceController) Simple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) { -// return i.module.Simple(ctx, keyword) -//} -// -//func (i *imlServiceController) MySimple(ctx *gin.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) { -// return i.module.MySimple(ctx, keyword) -//} - func (i *imlServiceController) Get(ctx *gin.Context, id string) (*service_dto.Service, error) { now := time.Now() defer func() { log.Infof("get service %s cost %d ms", id, time.Since(now).Milliseconds()) }() return i.module.Get(ctx, id) - } func (i *imlServiceController) Search(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) { @@ -336,3 +316,23 @@ func (i *imlAppController) GetApp(ctx *gin.Context, appId string) (*service_dto. func (i *imlAppController) DeleteApp(ctx *gin.Context, appId string) error { return i.module.DeleteApp(ctx, appId) } + +func newAIUpstream(id string, provider string, uri model_runtime.IProviderURI) *upstream_dto.Upstream { + return &upstream_dto.Upstream{ + Type: "http", + Balance: "round-robin", + Timeout: 300000, + Retry: 0, + Remark: fmt.Sprintf("auto create by ai service %s,provider is %s", id, provider), + LimitPeerSecond: 0, + ProxyHeaders: nil, + Scheme: uri.Scheme(), + PassHost: "node", + Nodes: []*upstream_dto.NodeConfig{ + { + Address: uri.Host(), + Weight: 100, + }, + }, + } +} diff --git a/controller/service/service.go b/controller/service/service.go index e64354e3..6f631781 100644 --- a/controller/service/service.go +++ b/controller/service/service.go @@ -24,12 +24,8 @@ type IServiceController interface { Delete(ctx *gin.Context, id string) error ServiceDoc(ctx *gin.Context, id string) (*service_dto.ServiceDoc, error) SaveServiceDoc(ctx *gin.Context, id string, input *service_dto.SaveServiceDoc) error - - //createAIService(ctx *gin.Context, teamID string, input *service_dto.CreateService) (*service_dto.Service, error) - //editAIService(ctx *gin.Context, id string, input *service_dto.EditService) (*service_dto.Service, error) - //DeleteAIService(ctx *gin.Context, id string) error - //SearchMyAIServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) - //SearchAIServices(ctx *gin.Context, teamID string, keyword string) ([]*service_dto.ServiceItem, error) + Simple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) + MySimple(ctx *gin.Context) ([]*service_dto.SimpleServiceItem, error) } type IAppController interface { diff --git a/controller/strategy/iml.go b/controller/strategy/iml.go index c8ed8c59..94786b07 100644 --- a/controller/strategy/iml.go +++ b/controller/strategy/iml.go @@ -1,9 +1,16 @@ package strategy import ( + "encoding/base64" "encoding/json" "fmt" + "sort" "strconv" + "time" + + "github.com/eolinker/go-common/utils" + + strategy_filter "github.com/APIParkLab/APIPark/strategy-filter" "github.com/APIParkLab/APIPark/module/service" "github.com/APIParkLab/APIPark/module/strategy" @@ -18,6 +25,105 @@ type imlStrategyController struct { serviceModule service.IServiceModule `autowired:""` } +func (i *imlStrategyController) ToPublish(ctx *gin.Context, driver string) ([]*strategy_dto.ToPublishItem, string, string, bool, error) { + list, err := i.strategyModule.ToPublish(ctx, driver) + if err != nil { + return nil, "", "", false, err + } + data, _ := json.Marshal(list) + source := base64.StdEncoding.EncodeToString(data) + return list, source, time.Now().Format("20060102150405") + "-release", len(list) > 0, nil +} + +func (i *imlStrategyController) FilterGlobalRemote(ctx *gin.Context, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) { + f, has := strategy_filter.RemoteFilter(name) + if !has { + return nil, nil, 0, "", "", fmt.Errorf("filter not found: %s", name) + } + scopeAllow := false + for _, s := range f.Scopes() { + if s == strategy_filter.ScopeGlobal { + scopeAllow = true + break + } + } + if !scopeAllow { + return nil, nil, 0, "", "", fmt.Errorf("scope not allowed: %s", name) + } + + list, total, err := f.RemoteList(ctx, "", nil, -1, -1) + if err != nil { + return nil, nil, 0, "", "", err + } + return utils.SliceToSlice(f.Titles(), func(l strategy_filter.OptionTitle) *strategy_dto.Title { + return &strategy_dto.Title{ + Field: l.Field, + Title: l.Title, + } + }), list, total, f.Key(), f.Key(), nil +} + +func (i *imlStrategyController) FilterServiceRemote(ctx *gin.Context, serviceId string, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) { + f, has := strategy_filter.RemoteFilter(name) + if !has { + return nil, nil, 0, "", "", fmt.Errorf("filter not found: %s", name) + } + scopeAllow := false + for _, s := range f.Scopes() { + if s == strategy_filter.ScopeService { + scopeAllow = true + break + } + } + if !scopeAllow { + return nil, nil, 0, "", "", fmt.Errorf("scope not allowed: %s", name) + } + list, total, err := f.RemoteList(ctx, "", map[string]interface{}{"service": serviceId}, -1, -1) + if err != nil { + return nil, nil, 0, "", "", err + } + return utils.SliceToSlice(f.Titles(), func(l strategy_filter.OptionTitle) *strategy_dto.Title { + return &strategy_dto.Title{ + Field: l.Field, + Title: l.Title, + } + }), list, total, f.Key(), "list", nil + +} + +func (i *imlStrategyController) filterOptions(ctx *gin.Context, scope string) ([]*strategy_dto.FilterOption, error) { + m, has := strategy_filter.Options(scope) + if !has { + return nil, fmt.Errorf("scope not found: %s", scope) + } + + list := utils.MapToSlice(m, func(key string, value *strategy_filter.Option) *strategy_dto.FilterOption { + pattern := "" + if value.Pattern != nil { + pattern = value.Pattern.String() + } + return &strategy_dto.FilterOption{ + Name: value.Name, + Title: value.Title, + Type: value.Type, + Pattern: pattern, + Options: value.Options, + } + }) + sort.Slice(list, func(i, j int) bool { + return list[i].Name < list[j].Name + }) + return list, nil +} + +func (i *imlStrategyController) FilterServiceOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) { + return i.filterOptions(ctx, strategy_filter.ScopeService) +} + +func (i *imlStrategyController) FilterGlobalOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) { + return i.filterOptions(ctx, strategy_filter.ScopeGlobal) +} + func (i *imlStrategyController) GetStrategy(ctx *gin.Context, id string) (*strategy_dto.Strategy, error) { return i.strategyModule.Get(ctx, id) } @@ -51,18 +157,18 @@ func (i *imlStrategyController) search(ctx *gin.Context, keyword string, scope s func (i *imlStrategyController) GlobalStrategyList(ctx *gin.Context, keyword string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) { - return i.search(ctx, keyword, strategy_dto.ToScope(strategy_dto.ScopeSystem), "", driver, page, pageSize, order, sort, filters) + return i.search(ctx, keyword, strategy_dto.ToScope(strategy_dto.ScopeGlobal), "", driver, page, pageSize, order, sort, filters) } func (i *imlStrategyController) CreateGlobalStrategy(ctx *gin.Context, driver string, input *strategy_dto.Create) error { input.Driver = driver - input.Scope = strategy_dto.ToScope(strategy_dto.ScopeSystem) + input.Scope = strategy_dto.ToScope(strategy_dto.ScopeGlobal) return i.strategyModule.Create(ctx, input) } -func (i *imlStrategyController) PublishGlobalStrategy(ctx *gin.Context) error { - return i.strategyModule.Publish(ctx, strategy_dto.ScopeSystem, "") +func (i *imlStrategyController) PublishGlobalStrategy(ctx *gin.Context, driver string) error { + return i.strategyModule.Publish(ctx, driver, strategy_dto.ScopeGlobal, "") } func (i *imlStrategyController) ServiceStrategyList(ctx *gin.Context, keyword string, serviceId string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) { @@ -87,7 +193,7 @@ func (i *imlStrategyController) EditStrategy(ctx *gin.Context, id string, input } func (i *imlStrategyController) EnableStrategy(ctx *gin.Context, id string) error { - return i.EnableStrategy(ctx, id) + return i.strategyModule.Enable(ctx, id) } func (i *imlStrategyController) DisableStrategy(ctx *gin.Context, id string) error { diff --git a/controller/strategy/strategy.go b/controller/strategy/strategy.go index 8dbb04fb..b6eac85a 100644 --- a/controller/strategy/strategy.go +++ b/controller/strategy/strategy.go @@ -11,7 +11,7 @@ import ( type IStrategyController interface { GlobalStrategyList(ctx *gin.Context, keyword string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) CreateGlobalStrategy(ctx *gin.Context, driver string, input *strategy_dto.Create) error - PublishGlobalStrategy(ctx *gin.Context) error + PublishGlobalStrategy(ctx *gin.Context, driver string) error ServiceStrategyList(ctx *gin.Context, keyword string, serviceId string, driver string, page string, pageSize string, order string, sort string, filters string) ([]*strategy_dto.StrategyItem, int64, error) CreateServiceStrategy(ctx *gin.Context, serviceId string, driver string, input *strategy_dto.Create) error @@ -22,6 +22,14 @@ type IStrategyController interface { DisableStrategy(ctx *gin.Context, id string) error DeleteStrategy(ctx *gin.Context, id string) error + + FilterGlobalOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) + FilterServiceOptions(ctx *gin.Context) ([]*strategy_dto.FilterOption, error) + + FilterGlobalRemote(ctx *gin.Context, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) + FilterServiceRemote(ctx *gin.Context, serviceId string, name string) ([]*strategy_dto.Title, []any, int64, string, string, error) + + ToPublish(ctx *gin.Context, driver string) ([]*strategy_dto.ToPublishItem, string, string, bool, error) } type IStrategyCommonController interface { diff --git a/module/monitor/dto/input.go b/module/monitor/dto/input.go index 7de6a169..7d01b8bd 100644 --- a/module/monitor/dto/input.go +++ b/module/monitor/dto/input.go @@ -24,7 +24,7 @@ type CommonInput struct { type StatisticInput struct { Apis []string `json:"apis"` - Projects []string `json:"projects"` + Services []string `json:"services"` Path string `json:"path"` *CommonInput } diff --git a/module/monitor/dto/output.go b/module/monitor/dto/output.go index 0b787bf8..5f6b1660 100644 --- a/module/monitor/dto/output.go +++ b/module/monitor/dto/output.go @@ -21,12 +21,12 @@ type ApiStatisticBasicItem struct { *MonCommonData } -type ProjectStatisticItem struct { - *ProjectStatisticBasicItem +type ServiceStatisticItem struct { + *ServiceStatisticBasicItem IsRed bool `json:"is_red"` //是否标红 } -type ProjectStatisticBasicItem struct { +type ServiceStatisticBasicItem struct { Id string `json:"id"` //订阅方ID Name string `json:"name"` //订阅方名称 *MonCommonData diff --git a/module/monitor/iml.go b/module/monitor/iml.go index 0179b1ec..6e497a49 100644 --- a/module/monitor/iml.go +++ b/module/monitor/iml.go @@ -5,14 +5,15 @@ import ( "encoding/json" "errors" "fmt" + "sort" + "time" + "github.com/APIParkLab/APIPark/gateway" "github.com/eolinker/eosc/log" "github.com/eolinker/go-common/auto" "github.com/eolinker/go-common/store" "github.com/eolinker/go-common/utils" "gorm.io/gorm" - "sort" - "time" "github.com/APIParkLab/APIPark/service/service" @@ -42,6 +43,442 @@ type imlMonitorStatisticModule struct { apiService api.IAPIService `autowired:""` } +func (i *imlMonitorStatisticModule) ApiStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) { + clusterId := cluster.DefaultClusterID + _, err := i.clusterService.Get(ctx, clusterId) + if err != nil { + return nil, err + } + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, err + } + + wm := make(map[string]interface{}) + if len(input.Apis) > 0 { + wm["uuid"] = input.Apis + wheres = append(wheres, monitor.MonWhereItem{ + Key: "api", + Operation: "in", + Values: input.Apis, + }) + } + if len(input.Services) > 0 { + wm["service"] = input.Services + wheres = append(wheres, monitor.MonWhereItem{ + Key: "project", + Operation: "in", + Values: input.Services, + }) + } + // 查询符合条件的API + apis, err := i.apiService.Search(ctx, input.Path, wm) + if err != nil { + return nil, err + } + if len(apis) < 1 { + // 没有符合条件的API + return make([]*monitor_dto.ApiStatisticBasicItem, 0), nil + } + apiIds := utils.SliceToSlice(apis, func(t *api.API) string { + return t.UUID + }) + + apiInfos, err := i.apiService.ListInfo(ctx, apiIds...) + if err != nil { + return nil, err + } + return i.apiStatistics(ctx, clusterId, apiInfos, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0) +} + +func (i *imlMonitorStatisticModule) apiStatistics(ctx context.Context, clusterId string, apiInfos []*api.Info, start time.Time, end time.Time, wheres []monitor.MonWhereItem, limit int) ([]*monitor_dto.ApiStatisticBasicItem, error) { + statisticMap, err := i.statistics(ctx, clusterId, "api", start, end, wheres, limit) + if err != nil { + return nil, err + } + + result := make([]*monitor_dto.ApiStatisticBasicItem, 0, len(statisticMap)) + for _, item := range apiInfos { + + statisticItem := &monitor_dto.ApiStatisticBasicItem{ + Id: item.UUID, + Name: item.Name, + Path: item.Path, + Service: auto.UUID(item.Service), + MonCommonData: new(monitor_dto.MonCommonData), + } + if val, ok := statisticMap[item.UUID]; ok { + statisticItem.MonCommonData = monitor_dto.ToMonCommonData(val) + delete(statisticMap, item.UUID) + } + result = append(result, statisticItem) + } + for key, item := range statisticMap { + statisticItem := &monitor_dto.ApiStatisticBasicItem{ + Id: key, + Name: "未知API-" + key, + MonCommonData: monitor_dto.ToMonCommonData(item), + } + + if key == "-" { + statisticItem.Name = "无API" + } + result = append(result, statisticItem) + } + sort.Slice(result, func(i, j int) bool { + return result[i].RequestTotal > result[j].RequestTotal + }) + return result, nil +} + +func (i *imlMonitorStatisticModule) SubscriberStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) { + clusterId := cluster.DefaultClusterID + _, err := i.clusterService.Get(ctx, clusterId) + if err != nil { + return nil, err + } + + apps, err := i.serviceService.AppList(ctx, input.Services...) + if err != nil { + return nil, err + } + appIds := utils.SliceToSlice(apps, func(p *service.Service) string { + return p.Id + }) + + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, err + } + + if len(appIds) > 0 { + wheres = append(wheres, monitor.MonWhereItem{ + Key: "app", + Operation: "in", + Values: appIds, + }) + } + + return i.serviceStatistics(ctx, clusterId, apps, "app", formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0) +} + +func (i *imlMonitorStatisticModule) serviceStatistics(ctx context.Context, clusterId string, services []*service.Service, groupBy string, start time.Time, end time.Time, wheres []monitor.MonWhereItem, limit int) ([]*monitor_dto.ServiceStatisticBasicItem, error) { + statisticMap, err := i.statistics(ctx, clusterId, groupBy, start, end, wheres, limit) + if err != nil { + return nil, err + } + + result := make([]*monitor_dto.ServiceStatisticBasicItem, 0, len(statisticMap)) + for _, item := range services { + statisticItem := &monitor_dto.ServiceStatisticBasicItem{ + Id: item.Id, + Name: item.Name, + MonCommonData: new(monitor_dto.MonCommonData), + } + if val, ok := statisticMap[item.Id]; ok { + statisticItem.MonCommonData = monitor_dto.ToMonCommonData(val) + delete(statisticMap, item.Id) + } + result = append(result, statisticItem) + } + for key, item := range statisticMap { + statisticItem := &monitor_dto.ServiceStatisticBasicItem{ + Id: key, + Name: "未知-" + key, + MonCommonData: monitor_dto.ToMonCommonData(item), + } + + if key == "-" { + statisticItem.Name = "-" + } + result = append(result, statisticItem) + } + sort.Slice(result, func(i, j int) bool { + return result[i].RequestTotal > result[j].RequestTotal + }) + return result, nil +} + +func (i *imlMonitorStatisticModule) ProviderStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) { + clusterId := cluster.DefaultClusterID + _, err := i.clusterService.Get(ctx, clusterId) + if err != nil { + return nil, err + } + + services, err := i.serviceService.ServiceList(ctx, input.Services...) + if err != nil { + return nil, err + } + + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, err + } + + if len(input.Services) > 0 { + wheres = append(wheres, monitor.MonWhereItem{ + Key: "provider", + Operation: "in", + Values: input.Services, + }) + } + + return i.serviceStatistics(ctx, clusterId, services, "provider", formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0) +} + +func (i *imlMonitorStatisticModule) APITrend(ctx context.Context, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) { + clusterId := cluster.DefaultClusterID + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, "", err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "api", + Operation: "=", + Values: []string{apiId}, + }) + executor, err := i.getExecutor(ctx, clusterId) + if err != nil { + return nil, "", err + } + result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres) + if err != nil { + return nil, "", err + } + return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil +} + +func (i *imlMonitorStatisticModule) ProviderTrend(ctx context.Context, providerId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) { + clusterId := cluster.DefaultClusterID + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, "", err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "provider", + Operation: "=", + Values: []string{providerId}, + }) + executor, err := i.getExecutor(ctx, clusterId) + if err != nil { + return nil, "", err + } + result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres) + if err != nil { + return nil, "", err + } + return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil +} + +func (i *imlMonitorStatisticModule) SubscriberTrend(ctx context.Context, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) { + clusterId := cluster.DefaultClusterID + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, "", err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "app", + Operation: "=", + Values: []string{subscriberId}, + }) + executor, err := i.getExecutor(ctx, clusterId) + if err != nil { + return nil, "", err + } + result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres) + if err != nil { + return nil, "", err + } + return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil +} + +func (i *imlMonitorStatisticModule) InvokeTrendWithSubscriberAndApi(ctx context.Context, apiId string, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) { + clusterId := cluster.DefaultClusterID + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, "", err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "api", + Operation: "=", + Values: []string{apiId}, + }, monitor.MonWhereItem{ + Key: "app", + Operation: "=", + Values: []string{subscriberId}, + }) + executor, err := i.getExecutor(ctx, clusterId) + if err != nil { + return nil, "", err + } + result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres) + if err != nil { + return nil, "", err + } + return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil +} + +func (i *imlMonitorStatisticModule) InvokeTrendWithProviderAndApi(ctx context.Context, providerId string, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) { + clusterId := cluster.DefaultClusterID + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, "", err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "api", + Operation: "=", + Values: []string{apiId}, + }, monitor.MonWhereItem{ + Key: "provider", + Operation: "=", + Values: []string{providerId}, + }) + executor, err := i.getExecutor(ctx, clusterId) + if err != nil { + return nil, "", err + } + result, timeInterval, err := executor.InvokeTrend(ctx, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres) + if err != nil { + return nil, "", err + } + return monitor_dto.ToMonInvokeCountTrend(result), timeInterval, nil +} + +func (i *imlMonitorStatisticModule) statisticOnApi(ctx context.Context, clusterId string, apiId string, groupBy string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) { + _, err := i.clusterService.Get(ctx, clusterId) + if err != nil { + return nil, err + } + var service []*service.Service + switch groupBy { + case "app": + service, err = i.serviceService.AppList(ctx) + case "provider": + service, err = i.serviceService.ServiceList(ctx) + default: + return nil, errors.New("invalid group by") + } + if err != nil { + return nil, err + } + + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "api", + Operation: "=", + Values: []string{apiId}, + }) + + statisticMap, err := i.statistics(ctx, clusterId, groupBy, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0) + if err != nil { + return nil, err + } + + result := make([]*monitor_dto.ServiceStatisticBasicItem, 0, len(statisticMap)) + for _, item := range service { + + statisticItem := &monitor_dto.ServiceStatisticBasicItem{ + Id: item.Id, + Name: item.Name, + MonCommonData: new(monitor_dto.MonCommonData), + } + if val, ok := statisticMap[item.Id]; ok { + statisticItem.MonCommonData = monitor_dto.ToMonCommonData(val) + delete(statisticMap, item.Id) + } + result = append(result, statisticItem) + } + for key, item := range statisticMap { + statisticItem := &monitor_dto.ServiceStatisticBasicItem{ + Id: key, + Name: "未知-" + key, + MonCommonData: monitor_dto.ToMonCommonData(item), + } + + if key == "-" { + statisticItem.Name = "-" + } + result = append(result, statisticItem) + } + sort.Slice(result, func(i, j int) bool { + return result[i].RequestTotal > result[j].RequestTotal + }) + return result, nil +} + +func (i *imlMonitorStatisticModule) ProviderStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) { + clusterId := cluster.DefaultClusterID + return i.statisticOnApi(ctx, clusterId, apiId, "provider", input) +} + +func (i *imlMonitorStatisticModule) ApiStatisticsOnProvider(ctx context.Context, providerId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) { + clusterId := cluster.DefaultClusterID + _, err := i.clusterService.Get(ctx, clusterId) + if err != nil { + return nil, err + } + + apiInfos, err := i.apiService.ListInfoForService(ctx, providerId) + if err != nil { + return nil, err + } + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "provider", + Operation: "=", + Values: []string{providerId}, + }) + + return i.apiStatistics(ctx, clusterId, apiInfos, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0) +} + +func (i *imlMonitorStatisticModule) ApiStatisticsOnSubscriber(ctx context.Context, subscriberId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) { + clusterId := cluster.DefaultClusterID + _, err := i.clusterService.Get(ctx, clusterId) + if err != nil { + return nil, err + } + // 根据订阅ID查询订阅的服务列表 + subscriptions, err := i.subscribeService.MySubscribeServices(ctx, subscriberId, nil) + if err != nil { + return nil, err + } + serviceIds := utils.SliceToSlice(subscriptions, func(t *subscribe.Subscribe) string { + return t.Service + }) + if len(serviceIds) < 1 { + return nil, nil + } + apiInfos, err := i.apiService.ListInfoForServices(ctx, serviceIds...) + if err != nil { + return nil, err + } + + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, err + } + wheres = append(wheres, monitor.MonWhereItem{ + Key: "app", + Operation: "=", + Values: []string{subscriberId}, + }) + + return i.apiStatistics(ctx, clusterId, apiInfos, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, 0) +} + +func (i *imlMonitorStatisticModule) SubscriberStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) { + clusterId := cluster.DefaultClusterID + return i.statisticOnApi(ctx, clusterId, apiId, "app", input) +} + func (i *imlMonitorStatisticModule) MessageTrend(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonMessageTrend, string, error) { clusterId := cluster.DefaultClusterID wheres, err := i.genCommonWheres(ctx, clusterId) @@ -173,7 +610,7 @@ func (i *imlMonitorStatisticModule) TopAPIStatistics(ctx context.Context, limit } -func (i *imlMonitorStatisticModule) TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error) { +func (i *imlMonitorStatisticModule) TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error) { clusterId := cluster.DefaultClusterID _, err := i.clusterService.Get(ctx, clusterId) if err != nil { @@ -182,7 +619,7 @@ func (i *imlMonitorStatisticModule) TopSubscriberStatistics(ctx context.Context, return i.topProjectStatistics(ctx, clusterId, "app", input, limit) } -func (i *imlMonitorStatisticModule) TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error) { +func (i *imlMonitorStatisticModule) TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error) { clusterId := cluster.DefaultClusterID _, err := i.clusterService.Get(ctx, clusterId) if err != nil { @@ -191,12 +628,12 @@ func (i *imlMonitorStatisticModule) TopProviderStatistics(ctx context.Context, l return i.topProjectStatistics(ctx, clusterId, "provider", input, limit) } -func (i *imlMonitorStatisticModule) topProjectStatistics(ctx context.Context, partitionId string, groupBy string, input *monitor_dto.CommonInput, limit int) ([]*monitor_dto.ProjectStatisticItem, error) { - wheres, err := i.genCommonWheres(ctx, partitionId) +func (i *imlMonitorStatisticModule) topProjectStatistics(ctx context.Context, clusterId string, groupBy string, input *monitor_dto.CommonInput, limit int) ([]*monitor_dto.ServiceStatisticItem, error) { + wheres, err := i.genCommonWheres(ctx, clusterId) if err != nil { return nil, err } - statisticMap, err := i.statistics(ctx, partitionId, groupBy, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, limit) + statisticMap, err := i.statistics(ctx, clusterId, groupBy, formatTimeByMinute(input.Start), formatTimeByMinute(input.End), wheres, limit) if err != nil { return nil, err } @@ -216,10 +653,10 @@ func (i *imlMonitorStatisticModule) topProjectStatistics(ctx context.Context, pa return t.Id }) - result := make([]*monitor_dto.ProjectStatisticItem, 0, len(statisticMap)) + result := make([]*monitor_dto.ServiceStatisticItem, 0, len(statisticMap)) for key, item := range statisticMap { - statisticItem := &monitor_dto.ProjectStatisticItem{ - ProjectStatisticBasicItem: &monitor_dto.ProjectStatisticBasicItem{ + statisticItem := &monitor_dto.ServiceStatisticItem{ + ServiceStatisticBasicItem: &monitor_dto.ServiceStatisticBasicItem{ Id: key, MonCommonData: monitor_dto.ToMonCommonData(item), }, @@ -407,7 +844,6 @@ func (m *imlMonitorConfig) GetMonitorConfig(ctx context.Context) (*monitor_dto.M Driver: info.Driver, Config: cfg, }, nil - return nil, nil } func (m *imlMonitorConfig) GetMonitorCluster(ctx context.Context) ([]*monitor_dto.MonitorCluster, error) { diff --git a/module/monitor/monitor.go b/module/monitor/monitor.go index 5a676efb..ba897c70 100644 --- a/module/monitor/monitor.go +++ b/module/monitor/monitor.go @@ -13,18 +13,36 @@ import ( type IMonitorStatisticModule interface { TopAPIStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ApiStatisticItem, error) - TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error) - TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ProjectStatisticItem, error) + TopProviderStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error) + TopSubscriberStatistics(ctx context.Context, limit int, input *monitor_dto.CommonInput) ([]*monitor_dto.ServiceStatisticItem, error) // RequestSummary 请求概况 RequestSummary(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonSummaryOutput, error) // ProxySummary 转发概况 ProxySummary(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonSummaryOutput, error) - // InvokeTrend 调用次数趋势 InvokeTrend(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) - // MessageTrend 消息趋势 MessageTrend(ctx context.Context, input *monitor_dto.CommonInput) (*monitor_dto.MonMessageTrend, string, error) + + ApiStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) + + SubscriberStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) + + ProviderStatistics(ctx context.Context, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) + + APITrend(ctx context.Context, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) + + ProviderTrend(ctx context.Context, providerId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) + + SubscriberTrend(ctx context.Context, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) + + InvokeTrendWithSubscriberAndApi(ctx context.Context, apiId string, subscriberId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) + InvokeTrendWithProviderAndApi(ctx context.Context, providerId string, apiId string, input *monitor_dto.CommonInput) (*monitor_dto.MonInvokeCountTrend, string, error) + + ProviderStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) + ApiStatisticsOnProvider(ctx context.Context, providerId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) + ApiStatisticsOnSubscriber(ctx context.Context, subscriberId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ApiStatisticBasicItem, error) + SubscriberStatisticsOnApi(ctx context.Context, apiId string, input *monitor_dto.StatisticInput) ([]*monitor_dto.ServiceStatisticBasicItem, error) } type IMonitorConfigModule interface { diff --git a/module/router/dto/output.go b/module/router/dto/output.go index 53988c2b..45fe4888 100644 --- a/module/router/dto/output.go +++ b/module/router/dto/output.go @@ -26,7 +26,8 @@ type Item struct { type SimpleItem struct { Id string `json:"id"` Methods []string `json:"methods"` - Path string `json:"request_path"` + //Name string `json:"name"` + Path string `json:"request_path"` } type Detail struct { diff --git a/module/router/filter.go b/module/router/filter.go new file mode 100644 index 00000000..5c4c47a0 --- /dev/null +++ b/module/router/filter.go @@ -0,0 +1,112 @@ +package router + +import ( + "context" + + router_dto "github.com/APIParkLab/APIPark/module/router/dto" + "github.com/eolinker/go-common/utils" + + "github.com/eolinker/eosc/log" + + "github.com/APIParkLab/APIPark/service/api" + strategy_filter "github.com/APIParkLab/APIPark/strategy-filter" +) + +var _ strategy_filter.IRemoteFilter = (*imlRouterFilter)(nil) + +type imlRouterFilter struct { + service api.IAPIService `autowired:""` +} + +func (i *imlRouterFilter) Name() string { + return "api" +} + +func (i *imlRouterFilter) Title() string { + return "API" +} + +func (i *imlRouterFilter) Labels(values ...string) []string { + list, err := i.service.ListInfo(context.Background(), values...) + if err != nil { + log.Errorf("get api labels error: %v", err) + return nil + } + + return utils.SliceToSlice(list, func(a *api.Info) string { + return a.Name + }) +} + +func (i *imlRouterFilter) Type() string { + return strategy_filter.TypeRemote +} + +func (i *imlRouterFilter) Scopes() []string { + return []string{ + //strategy_filter.ScopeGlobal, + strategy_filter.ScopeService, + } +} + +func (i *imlRouterFilter) Option() *strategy_filter.Option { + return &strategy_filter.Option{ + Name: i.Name(), + Title: i.Title(), + Type: i.Type(), + } +} + +func (i *imlRouterFilter) Titles() []strategy_filter.OptionTitle { + return []strategy_filter.OptionTitle{ + { + Field: "name", + Title: "api name", + }, + { + Field: "methods", + Title: "methods", + }, + { + Field: "request_path", + Title: "request path", + }, + } +} + +func (i *imlRouterFilter) Key() string { + return "id" +} + +func (i *imlRouterFilter) Target() string { + return "list" +} + +func (i *imlRouterFilter) RemoteList(ctx context.Context, keyword string, condition map[string]interface{}, page int, pageSize int) ([]any, int64, error) { + if pageSize == -1 { + // 获取全部 + list, err := i.service.Search(ctx, keyword, condition) + if err != nil { + return nil, 0, err + } + + return utils.SliceToSlice(list, func(s *api.API) any { + return &router_dto.SimpleItem{ + Id: s.UUID, + Path: s.Path, + Methods: s.Method, + } + }), int64(len(list)), nil + } + list, total, err := i.service.SearchByPage(ctx, keyword, condition, page, pageSize, "update_at") + if err != nil { + return nil, 0, err + } + return utils.SliceToSlice(list, func(s *api.API) any { + return &router_dto.SimpleItem{ + Id: s.UUID, + Path: s.Path, + Methods: s.Method, + } + }), total, nil +} diff --git a/module/router/router.go b/module/router/router.go index 1a43b958..36c83273 100644 --- a/module/router/router.go +++ b/module/router/router.go @@ -2,9 +2,11 @@ package router import ( "context" - "github.com/APIParkLab/APIPark/module/system" "reflect" + "github.com/APIParkLab/APIPark/module/system" + strategy_filter "github.com/APIParkLab/APIPark/strategy-filter" + "github.com/eolinker/go-common/autowire" router_dto "github.com/APIParkLab/APIPark/module/router/dto" @@ -45,4 +47,8 @@ func init() { autowire.Auto[IExportRouterModule](func() reflect.Value { return reflect.ValueOf(apiModule) }) + + filter := new(imlRouterFilter) + autowire.Autowired(filter) + strategy_filter.RegisterRemoteFilter(filter) } diff --git a/module/service/filter.go b/module/service/filter.go new file mode 100644 index 00000000..4304a266 --- /dev/null +++ b/module/service/filter.go @@ -0,0 +1,125 @@ +package service + +import ( + "context" + + service_dto "github.com/APIParkLab/APIPark/module/service/dto" + "github.com/eolinker/go-common/auto" + + "github.com/APIParkLab/APIPark/service/service" + "github.com/eolinker/eosc/log" + "github.com/eolinker/go-common/utils" + + strategy_filter "github.com/APIParkLab/APIPark/strategy-filter" +) + +var _ strategy_filter.IRemoteFilter = (*imlAppFilter)(nil) + +type imlAppFilter struct { + service service.IServiceService `autowired:""` +} + +func (i *imlAppFilter) Name() string { + return "application" +} + +func (i *imlAppFilter) Title() string { + return "消费者" +} + +func (i *imlAppFilter) Labels(values ...string) []string { + if len(values) == 0 { + return nil + } + if values[0] == strategy_filter.ValuesALL { + return []string{ + "全部消费者", + } + } + apps, err := i.service.AppList(context.Background(), values...) + if err != nil { + log.Error(err) + return nil + } + return utils.SliceToSlice(apps, func(a *service.Service) string { + return a.Name + }) +} + +func (i *imlAppFilter) Type() string { + return strategy_filter.TypeRemote +} + +func (i *imlAppFilter) Scopes() []string { + return []string{ + strategy_filter.ScopeGlobal, + strategy_filter.ScopeService, + } +} + +func (i *imlAppFilter) Option() *strategy_filter.Option { + return &strategy_filter.Option{ + Name: i.Name(), + Title: i.Title(), + Type: i.Type(), + } +} + +func (i *imlAppFilter) Titles() []strategy_filter.OptionTitle { + return []strategy_filter.OptionTitle{ + { + Field: "name", + Title: "consumer", + }, + { + Field: "id", + Title: "consumer id", + }, + { + Field: "description", + Title: "description", + }, + } +} + +func (i *imlAppFilter) Key() string { + return "id" +} + +func (i *imlAppFilter) Target() string { + return "list" +} + +func (i *imlAppFilter) RemoteList(ctx context.Context, keyword string, condition map[string]interface{}, page int, pageSize int) ([]any, int64, error) { + if condition == nil { + condition = make(map[string]interface{}) + } + condition["as_app"] = true + if pageSize == -1 { + // 获取全部 + list, err := i.service.Search(ctx, keyword, condition, "update_at") + if err != nil { + return nil, 0, err + } + return utils.SliceToSlice(list, func(s *service.Service) any { + return &service_dto.SimpleAppItem{ + Id: s.Id, + Name: s.Name, + Team: auto.UUID(s.Team), + Description: s.Description, + } + }), int64(len(list)), nil + } + list, total, err := i.service.SearchByPage(ctx, keyword, condition, page, pageSize, "update_at") + if err != nil { + return nil, 0, err + } + return utils.SliceToSlice(list, func(s *service.Service) any { + return &service_dto.SimpleAppItem{ + Id: s.Id, + Name: s.Name, + Team: auto.UUID(s.Team), + Description: s.Description, + } + }), total, nil +} diff --git a/module/service/iml.go b/module/service/iml.go index 64187a18..652c1a97 100644 --- a/module/service/iml.go +++ b/module/service/iml.go @@ -109,9 +109,7 @@ func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.Export Catalogue: s.Catalogue, Logo: s.Logo, } - //if v, ok := docMap[s.Id]; ok { - // info.Doc = v.Doc - //} + if tags, ok := serviceTagMap[s.Id]; ok { info.Tags = tags } @@ -191,47 +189,47 @@ func (i *imlServiceModule) SearchMyServices(ctx context.Context, teamId string, // }), nil //} -//func (i *imlServiceModule) Simple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) { -// w := make(map[string]interface{}) -// w["as_server"] = true -// -// services, err := i.serviceService.Search(ctx, keyword, w) -// if err != nil { -// return nil, err -// } -// -// items := make([]*service_dto.SimpleServiceItem, 0, len(services)) -// for _, p := range services { -// -// items = append(items, &service_dto.SimpleServiceItem{ -// Id: p.Id, -// Name: p.Name, -// Description: p.Description, -// Team: auto.UUID(p.Team), -// }) -// } -// return items, nil -//} -// -//func (i *imlServiceModule) MySimple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) { -// services, err := i.searchMyServices(ctx, "", keyword) -// -// if err != nil { -// return nil, err -// } -// -// items := make([]*service_dto.SimpleServiceItem, 0, len(services)) -// for _, p := range services { -// -// items = append(items, &service_dto.SimpleServiceItem{ -// Id: p.Id, -// Name: p.Name, -// Description: p.Description, -// Team: auto.UUID(p.Team), -// }) -// } -// return items, nil -//} +func (i *imlServiceModule) Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) { + w := make(map[string]interface{}) + w["as_server"] = true + + services, err := i.serviceService.Search(ctx, "", w) + if err != nil { + return nil, err + } + + items := make([]*service_dto.SimpleServiceItem, 0, len(services)) + for _, p := range services { + + items = append(items, &service_dto.SimpleServiceItem{ + Id: p.Id, + Name: p.Name, + Description: p.Description, + Team: auto.UUID(p.Team), + }) + } + return items, nil +} + +func (i *imlServiceModule) MySimple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) { + services, err := i.searchMyServices(ctx, "", "") + + if err != nil { + return nil, err + } + + items := make([]*service_dto.SimpleServiceItem, 0, len(services)) + for _, p := range services { + + items = append(items, &service_dto.SimpleServiceItem{ + Id: p.Id, + Name: p.Name, + Description: p.Description, + Team: auto.UUID(p.Team), + }) + } + return items, nil +} func (i *imlServiceModule) Get(ctx context.Context, id string) (*service_dto.Service, error) { now := time.Now() diff --git a/module/service/module.go b/module/service/module.go index 31afdede..bbcc749f 100644 --- a/module/service/module.go +++ b/module/service/module.go @@ -4,6 +4,8 @@ import ( "context" "reflect" + strategy_filter "github.com/APIParkLab/APIPark/strategy-filter" + "github.com/APIParkLab/APIPark/module/system" service_dto "github.com/APIParkLab/APIPark/module/service/dto" @@ -25,11 +27,11 @@ type IServiceModule interface { // Delete 删除项目 Delete(ctx context.Context, id string) error - // Simple 获取简易项目列表 - //Simple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) + //Simple 获取简易项目列表 + Simple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) - // MySimple 获取我的简易项目列表 - //MySimple(ctx context.Context, keyword string) ([]*service_dto.SimpleServiceItem, error) + //MySimple 获取我的简易项目列表 + MySimple(ctx context.Context) ([]*service_dto.SimpleServiceItem, error) } type IServiceDocModule interface { @@ -83,4 +85,8 @@ func init() { return reflect.ValueOf(serviceDocModule) }) + filter := new(imlAppFilter) + autowire.Autowired(filter) + strategy_filter.RegisterRemoteFilter(filter) + } diff --git a/module/strategy/driver/filter.go b/module/strategy/driver/filter.go deleted file mode 100644 index 0fdd9b6a..00000000 --- a/module/strategy/driver/filter.go +++ /dev/null @@ -1,132 +0,0 @@ -package strategy_driver - -import ( - "fmt" - "regexp" - - strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto" -) - -type FilterOptionsItem struct { - Name string - Title string - Type string - Pattern *regexp.Regexp - Options []string -} - -const ( - FilterMethod = "method" - FilterPath = "path" - FilterIP = "ip" - FilterApplication = "application" - FilterApi = "api" - FilterService = "service" - FilterAppKey = "appkey" - - FilterTypeRemote = "remote" - FilterTypePattern = "pattern" - FilterTypeStatic = "static" - - FilterValuesALL = "ALL" -) - -const ( - ApiPathRegexp = `^\*?[\w-/]+\*?$` - CIDRIpv4Exp = `^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([1-9]|[1-2]\d|3[0-2]))?$` -) - -const ( - HttpALL = "ALL" - HttpGET = "GET" - HttpPOST = "POST" - HttpPUT = "PUT" - HttpDELETE = "DELETE" - HttpPATCH = "PATCH" - HttpHEADER = "HEADER" - HttpOPTIONS = "OPTIONS" -) - -var ( - staticOptions = []*FilterOptionsItem{ - { - Name: FilterMethod, - Title: "API请求方式", - Type: FilterTypeStatic, - Options: []string{HttpALL, HttpGET, HttpPOST, HttpPUT, HttpDELETE, HttpPATCH, HttpHEADER, HttpOPTIONS}, - }, { - Name: FilterPath, - Title: "API路径", - Type: FilterTypePattern, - Pattern: regexp.MustCompile(ApiPathRegexp), - }, { - Name: FilterIP, - Title: "IP", - Type: FilterTypePattern, - Pattern: regexp.MustCompile(CIDRIpv4Exp), - }, - } - globalFilters = map[string]struct{}{} - serviceFilters = map[string]struct{}{} -) - -func init() { - for _, option := range staticOptions { - globalFilters[option.Name] = struct{}{} - } - for _, option := range staticOptions { - serviceFilters[option.Name] = struct{}{} - } -} - -func CheckFilters(name string, scope strategy_dto.Scope, filters []*strategy_dto.Filter) error { - var fs map[string]struct{} - switch scope.String() { - case strategy_dto.ScopeSystem: - fs = globalFilters - case strategy_dto.ScopeService: - fs = serviceFilters - default: - return fmt.Errorf("unknown scope %s", scope) - } - filterNameSet := make(map[string]struct{}) - for _, filter := range filters { - _, ok := fs[filter.Name] - if !ok { - return fmt.Errorf("%s filter %s not found", name, filter.Name) - } - if len(filter.Values) == 0 { - return fmt.Errorf("%s.Options can't be null. filter.Name:%s ", name, filter.Name) - } - - if _, has := filterNameSet[filter.Name]; has { - return fmt.Errorf("%s.Name %s is reduplicative. ", name, filter.Name) - } - filterNameSet[filter.Name] = struct{}{} - } - - return nil -} - -type OptionTitle struct { - Field string `json:"field"` - Title string `json:"title" aoi18n:""` -} - -type FilterOptionConfig struct { - Title string - Titles []OptionTitle - Key string -} - -type IFilterOptionHandler interface { - Name() string - Config() FilterOptionConfig - GetOptions(keyword, conditions map[string]interface{}, pageNum, pageSize int) ([]any, int) - Labels(values ...string) []string - Label(value string) string -} - -type IFilterFactory interface { - GetHandler(name string) IFilterOptionHandler -} diff --git a/module/strategy/dto/enum.go b/module/strategy/dto/enum.go index 9ba86462..47932b4b 100644 --- a/module/strategy/dto/enum.go +++ b/module/strategy/dto/enum.go @@ -1,7 +1,7 @@ package strategy_dto const ( - ScopeSystem = "system" + ScopeGlobal = "global" ScopeTeam = "team" ScopeService = "service" @@ -16,13 +16,13 @@ type Scope int func (s Scope) String() string { switch s { case 0: - return ScopeSystem + return ScopeGlobal case 1: return ScopeTeam case 2: return ScopeService default: - return ScopeSystem + return ScopeGlobal } } @@ -32,7 +32,7 @@ func (s Scope) Int() int { func ToScope(s string) Scope { switch s { - case ScopeSystem: + case ScopeGlobal: return 0 case ScopeTeam: return 1 diff --git a/module/strategy/dto/output.go b/module/strategy/dto/output.go index 5c4727de..e299f257 100644 --- a/module/strategy/dto/output.go +++ b/module/strategy/dto/output.go @@ -2,12 +2,13 @@ package strategy_dto import ( "encoding/json" + "time" "github.com/APIParkLab/APIPark/service/strategy" "github.com/eolinker/go-common/auto" ) -func ToStrategyItem(s *strategy.Strategy, publishVersion string) *StrategyItem { +func StrategyStatus(s *strategy.Strategy, publishVersion string) string { publishStatus := PublishStatusOffline if publishVersion != "" { if s.IsDelete { @@ -21,17 +22,36 @@ func ToStrategyItem(s *strategy.Strategy, publishVersion string) *StrategyItem { } } } + return publishStatus +} + +func ToStrategyItem(s *strategy.Strategy, publishVersion string, filters string) *StrategyItem { + publishStatus := PublishStatusOffline + if publishVersion != "" { + if s.IsDelete { + publishStatus = PublishStatusDelete + } else { + version := s.UpdateAt.Format("20060102150405") + if version != publishVersion { + publishStatus = PublishStatusUpdate + } else { + publishStatus = PublishStatusOnline + } + } + } + return &StrategyItem{ Id: s.Id, Name: s.Name, - Priority: 0, + Priority: s.Priority, Desc: s.Desc, - Filters: "", + Filters: filters, Updater: auto.UUID(s.Updater), UpdateTime: auto.TimeLabel(s.UpdateAt), ProcessedTotal: 0, PublishStatus: publishStatus, IsStop: s.IsStop, + IsDelete: s.IsDelete, } } @@ -70,4 +90,25 @@ type StrategyItem struct { ProcessedTotal int `json:"processed_total"` PublishStatus string `json:"publish_status"` IsStop bool `json:"is_stop"` + IsDelete bool `json:"is_delete"` +} + +type FilterOption struct { + Name string `json:"name"` + Title string `json:"title"` + Type string `json:"type"` + Pattern string `json:"pattern"` + Options []string `json:"options"` +} + +type Title struct { + Field string `json:"field"` + Title string `json:"title" aoi18n:""` +} + +type ToPublishItem struct { + Name string `json:"name"` + Priority int `json:"priority"` + Status string `json:"status"` + OptTime time.Time `json:"opt_time"` } diff --git a/module/strategy/iml.go b/module/strategy/iml.go index c6e7c756..8c37b631 100644 --- a/module/strategy/iml.go +++ b/module/strategy/iml.go @@ -3,7 +3,15 @@ package strategy import ( "context" "encoding/json" + "errors" "fmt" + "strings" + + "gorm.io/gorm" + + "github.com/eolinker/eosc/log" + + strategy_filter "github.com/APIParkLab/APIPark/strategy-filter" "github.com/eolinker/go-common/store" @@ -27,6 +35,34 @@ type imlStrategyModule struct { transaction store.ITransaction `autowired:""` } +func (i *imlStrategyModule) ToPublish(ctx context.Context, driver string) ([]*strategy_dto.ToPublishItem, error) { + scope := strategy_dto.ToScope(strategy_dto.ScopeGlobal) + list, err := i.strategyService.SearchAll(ctx, "", driver, scope.Int(), "") + if err != nil { + return nil, err + } + strategyIds := utils.SliceToSlice(list, func(l *strategy.Strategy) string { return l.Id }) + commits, err := i.strategyService.ListLatestStrategyCommit(ctx, scope.String(), "", strategyIds...) + if err != nil { + return nil, err + } + commitMap := utils.SliceToMapO(commits, func(c *commit.Commit[strategy.StrategyCommit]) (string, string) { return c.Key, c.Data.Version }) + items := make([]*strategy_dto.ToPublishItem, 0, len(list)) + for _, l := range list { + status := strategy_dto.StrategyStatus(l, commitMap[l.Id]) + if status == strategy_dto.PublishStatusOnline { + continue + } + items = append(items, &strategy_dto.ToPublishItem{ + Name: l.Name, + Priority: l.Priority, + Status: status, + OptTime: l.UpdateAt, + }) + } + return items, nil +} + func (i *imlStrategyModule) Search(ctx context.Context, keyword string, driver string, scope strategy_dto.Scope, target string, page int, pageSize int, filters []string, order ...string) ([]*strategy_dto.StrategyItem, int64, error) { list, total, err := i.strategyService.Search(ctx, keyword, driver, scope.Int(), target, page, pageSize, filters, order...) if err != nil { @@ -40,7 +76,19 @@ func (i *imlStrategyModule) Search(ctx context.Context, keyword string, driver s commitMap := utils.SliceToMapO(commits, func(c *commit.Commit[strategy.StrategyCommit]) (string, string) { return c.Key, c.Data.Version }) items := make([]*strategy_dto.StrategyItem, 0, len(list)) for _, l := range list { - item := strategy_dto.ToStrategyItem(l, commitMap[l.Id]) + fs := make([]*strategy_dto.Filter, 0) + + json.Unmarshal([]byte(l.Filters), &fs) + filterList := make([]string, 0, len(fs)) + for _, f := range fs { + info, err := strategy_filter.FilterLabel(f.Name, f.Values) + if err != nil { + log.Errorf("get filter label error: %v", err) + continue + } + filterList = append(filterList, fmt.Sprintf("[%s:%s]", info.Title, info.Label)) + } + item := strategy_dto.ToStrategyItem(l, commitMap[l.Id], strings.Join(filterList, ";")) items = append(items, item) } return items, total, nil @@ -51,7 +99,17 @@ func (i *imlStrategyModule) Get(ctx context.Context, id string) (*strategy_dto.S if err != nil { return nil, err } - return strategy_dto.ToStrategy(info), nil + s := strategy_dto.ToStrategy(info) + for _, f := range s.Filters { + ff, has := strategy_filter.FilterGet(f.Name) + if !has { + return nil, fmt.Errorf("filter not found: %s", f.Name) + } + f.Title = ff.Title() + f.Type = ff.Type() + f.Label = strings.Join(ff.Labels(f.Values...), ",") + } + return s, nil } func (i *imlStrategyModule) Create(ctx context.Context, input *strategy_dto.Create) error { @@ -65,7 +123,7 @@ func (i *imlStrategyModule) Create(ctx context.Context, input *strategy_dto.Crea if input.Priority < 1 { input.Priority = 1000 } - err := strategy_driver.CheckFilters(input.Driver, input.Scope, input.Filters) + err := strategy_filter.CheckFilters(input.Driver, input.Scope, input.Filters) if err != nil { return err } @@ -102,7 +160,7 @@ func (i *imlStrategyModule) Edit(ctx context.Context, id string, input *strategy } filters := info.Filters if input.Filters != nil { - err = strategy_driver.CheckFilters(info.Driver, strategy_dto.Scope(info.Scope), *input.Filters) + err = strategy_filter.CheckFilters(info.Driver, strategy_dto.Scope(info.Scope), *input.Filters) if err != nil { return err } @@ -138,8 +196,8 @@ func (i *imlStrategyModule) Disable(ctx context.Context, id string) error { return i.strategyService.Save(ctx, id, &strategy.Edit{IsStop: &stop}) } -func (i *imlStrategyModule) Publish(ctx context.Context, scope string, target string) error { - list, err := i.strategyService.AllByScope(ctx, strategy_dto.ToScope(scope).Int(), target) +func (i *imlStrategyModule) Publish(ctx context.Context, driver string, scope string, target string) error { + list, err := i.strategyService.AllByScope(ctx, driver, strategy_dto.ToScope(scope).Int(), target) if err != nil { return err } @@ -163,5 +221,12 @@ func (i *imlStrategyModule) Publish(ctx context.Context, scope string, target st } func (i *imlStrategyModule) Delete(ctx context.Context, id string) error { + _, err := i.strategyService.LatestStrategyCommit(ctx, strategy_dto.ScopeGlobal, "", id) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil + } + return i.strategyService.Delete(ctx, id) + } return i.strategyService.SortDelete(ctx, id) } diff --git a/module/strategy/module.go b/module/strategy/module.go index fde3e317..d35ee39a 100644 --- a/module/strategy/module.go +++ b/module/strategy/module.go @@ -17,8 +17,9 @@ type IStrategyModule interface { Edit(ctx context.Context, id string, i *strategy_dto.Edit) error Enable(ctx context.Context, id string) error Disable(ctx context.Context, id string) error - Publish(ctx context.Context, scope string, target string) error + Publish(ctx context.Context, driver string, scope string, target string) error Delete(ctx context.Context, id string) error + ToPublish(ctx context.Context, driver string) ([]*strategy_dto.ToPublishItem, error) } func init() { diff --git a/module/system/dto/input.go b/module/system/dto/input.go index 23230ba1..1b7d8f35 100644 --- a/module/system/dto/input.go +++ b/module/system/dto/input.go @@ -7,6 +7,7 @@ import ( type InputSetting struct { InvokeAddress string `json:"invoke_address" key:"system.node.invoke_address"` + SitePrefix string `json:"site_prefix" key:"system.setting.site_prefix"` } func (i *InputSetting) Validate() error { diff --git a/module/system/dto/output.go b/module/system/dto/output.go index b1d9a36d..d6ea63d3 100644 --- a/module/system/dto/output.go +++ b/module/system/dto/output.go @@ -7,6 +7,7 @@ import ( type Setting struct { InvokeAddress string `json:"invoke_address" key:"system.node.invoke_address"` + SitePrefix string `json:"site_prefix" key:"system.setting.site_prefix"` } func MapStringToStruct[T any](m map[string]string) *T { diff --git a/plugins/core/monitor.go b/plugins/core/monitor.go index 5b8a53e1..64afa039 100644 --- a/plugins/core/monitor.go +++ b/plugins/core/monitor.go @@ -17,5 +17,10 @@ func (p *plugin) monitorStatisticApis() []pm3.Api { pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/monitor/config", []string{"context", "body"}, []string{"info"}, p.monitorConfigController.SaveMonitorConfig, access.SystemSettingsDataSourceManager), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/monitor/config", []string{"context"}, []string{"info"}, p.monitorConfigController.GetMonitorConfig, access.SystemSettingsDataSourceView), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/monitor/clusters", []string{"context"}, []string{"clusters"}, p.monitorConfigController.GetMonitorCluster), + + pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/monitor/:data_type", []string{"context", "rest:data_type", "body"}, []string{"statistics"}, p.monitorStatisticController.Statistics), + pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/monitor/:data_type/trend", []string{"context", "rest:data_type", "query:id", "body"}, []string{"tendency", "time_interval"}, p.monitorStatisticController.InvokeTrend), + pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/monitor/:data_type/trend/:typ", []string{"context", "rest:data_type", "rest:typ", "query:api", "query:provider", "query:subscriber", "body"}, []string{"tendency", "time_interval"}, p.monitorStatisticController.InvokeTrendInner), + pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/monitor/:data_type/statistics/:typ", []string{"context", "rest:data_type", "rest:typ", "query:id", "body"}, []string{"statistics"}, p.monitorStatisticController.StatisticsInner), } } diff --git a/plugins/core/service.go b/plugins/core/service.go index 774ed852..d2ff224a 100644 --- a/plugins/core/service.go +++ b/plugins/core/service.go @@ -15,6 +15,8 @@ func (p *plugin) ServiceApis() []pm3.Api { pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/service/info", []string{"context", "query:service"}, nil, p.serviceController.Delete, access.SystemWorkspaceServiceManagerAll, access.TeamTeamServiceManager), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/my_services", []string{"context", "query:team", "query:keyword"}, []string{"services"}, p.serviceController.SearchMyServices), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/services", []string{"context", "query:team", "query:keyword"}, []string{"services"}, p.serviceController.Search, access.SystemWorkspaceServiceViewAll, access.TeamTeamServiceView), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/services", []string{"context"}, []string{"services"}, p.serviceController.Simple), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/services/mine", []string{"context"}, []string{"services"}, p.serviceController.MySimple), // 应用相关 pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/app/info", []string{"context", "query:app"}, []string{"app"}, p.appController.GetApp, access.SystemWorkspaceApplicationViewAll, access.TeamTeamConsumerView), pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/app", []string{"context", "query:app"}, nil, p.appController.DeleteApp, access.SystemWorkspaceApplicationManagerAll, access.TeamTeamConsumerManager), diff --git a/plugins/core/strategy.go b/plugins/core/strategy.go index aa811063..64e10396 100644 --- a/plugins/core/strategy.go +++ b/plugins/core/strategy.go @@ -13,13 +13,21 @@ func (p *plugin) strategyApis() []pm3.Api { pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/strategy/global/:driver", []string{"context", "rest:driver", "body"}, nil, p.strategyController.CreateGlobalStrategy), pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/strategy/global/:driver", []string{"context", "query:strategy", "body"}, nil, p.strategyController.EditStrategy), pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/strategy/global/:driver", []string{"context", "query:strategy"}, nil, p.strategyController.DeleteStrategy), - pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/strategy/global/:driver/enable", []string{"context", "query:strategy"}, nil, p.strategyController.EnableStrategy), - pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/strategy/global/:driver/disable", []string{"context", "query:strategy"}, nil, p.strategyController.DisableStrategy), + pm3.CreateApiWidthDoc(http.MethodPatch, "/api/v1/strategy/global/:driver/enable", []string{"context", "query:strategy"}, nil, p.strategyController.EnableStrategy), + pm3.CreateApiWidthDoc(http.MethodPatch, "/api/v1/strategy/global/:driver/disable", []string{"context", "query:strategy"}, nil, p.strategyController.DisableStrategy), + pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/strategy/global/:driver/publish", []string{"context", "rest:driver"}, nil, p.strategyController.PublishGlobalStrategy), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/strategy/service/:driver/list", []string{"context", "query:keyword", "query:service", "rest:driver", "query:page", "query:page_size", "query:order", "query:sort", "query:filters"}, []string{"list", "total"}, p.strategyController.ServiceStrategyList), pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/strategy/service/:driver", []string{"context", "query:strategy"}, []string{"strategy"}, p.strategyController.GetStrategy), pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/strategy/service/:driver", []string{"context", "query:service", "rest:driver", "body"}, nil, p.strategyController.CreateServiceStrategy), - pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/strategy/service/:driver/enable", []string{"context", "query:strategy"}, nil, p.strategyController.EnableStrategy), - pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/strategy/service/:driver/disable", []string{"context", "query:strategy"}, nil, p.strategyController.DisableStrategy), + pm3.CreateApiWidthDoc(http.MethodPatch, "/api/v1/strategy/service/:driver/enable", []string{"context", "query:strategy"}, nil, p.strategyController.EnableStrategy), + pm3.CreateApiWidthDoc(http.MethodPatch, "/api/v1/strategy/service/:driver/disable", []string{"context", "query:strategy"}, nil, p.strategyController.DisableStrategy), + + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/strategy/global/filter-options", []string{"context"}, []string{"options"}, p.strategyController.FilterGlobalOptions), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/strategy/service/filter-options", []string{"context"}, []string{"options"}, p.strategyController.FilterServiceOptions), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/strategy/filter-remote/:name", []string{"context", "rest:name"}, []string{"titles", "list", "total", "key", "value"}, p.strategyController.FilterGlobalRemote), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/strategy/service/filter-remote/:name", []string{"context", "query:service", "rest:name"}, []string{"titles", "list", "total", "key", "value"}, p.strategyController.FilterServiceRemote), + + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/strategy/global/:driver/to-publishs", []string{"context", "rest:driver"}, []string{"strategies", "source", "version_name", "is_publish"}, p.strategyController.ToPublish), } } diff --git a/resources/locale/i18n/zh-CN.json b/resources/locale/i18n/zh-CN.json index e9b000f4..52bf043b 100644 --- a/resources/locale/i18n/zh-CN.json +++ b/resources/locale/i18n/zh-CN.json @@ -5,12 +5,18 @@ "api": "API", "api doc": "API文档", "api gateway": "API网关", + "api method": "API请求方法", + "api name": "API名称", + "api path": "API路径", + "all method": "全部请求方式", "api portal": "API门户", "authorization": "访问授权", "consumer": "消费者", + "consumer id": "消费者ID", "consumer admin": "消费者管理员", "consumer developer": "消费者开发者", "create": "创建", + "description": "描述", "data source": "数据源", "devops admin": "运维管理员", "general": "常规设置", @@ -42,5 +48,7 @@ "view all service": "查看所有服务", "view all team": "查看所有团队", "view subscribed services": "查看已经订阅的服务", - "workspace": "工作空间" + "workspace": "工作空间", + "request path": "请求路径", + "methods": "请求方法" } diff --git a/service/api/iml.go b/service/api/iml.go index 359d02db..866bb841 100644 --- a/service/api/iml.go +++ b/service/api/iml.go @@ -41,6 +41,30 @@ type imlAPIService struct { universally.IServiceDelete } +func (i *imlAPIService) ListForServices(ctx context.Context, serviceIds ...string) ([]*API, error) { + w := map[string]interface{}{} + if len(serviceIds) > 0 { + w["service"] = serviceIds + } + list, err := i.store.List(ctx, w) + if err != nil { + return nil, err + } + return utils.SliceToSlice(list, FromEntity), nil +} + +func (i *imlAPIService) ListInfoForServices(ctx context.Context, serviceIds ...string) ([]*Info, error) { + w := map[string]interface{}{} + if len(serviceIds) > 0 { + w["service"] = serviceIds + } + list, err := i.apiInfoStore.List(ctx, w) + if err != nil { + return nil, err + } + return utils.SliceToSlice(list, FromEntityInfo), nil +} + func (i *imlAPIService) ListLatestCommitRequest(ctx context.Context, aid ...string) ([]*commit.Commit[Request], error) { return i.requestCommitService.ListLatest(ctx, aid...) } diff --git a/service/api/service.go b/service/api/service.go index 09911b0e..9c58c335 100644 --- a/service/api/service.go +++ b/service/api/service.go @@ -17,9 +17,11 @@ type IAPIService interface { CountMapByService(ctx context.Context, service ...string) (map[string]int64, error) Exist(ctx context.Context, aid string, api *Exist) error ListForService(ctx context.Context, serviceId string) ([]*API, error) + ListForServices(ctx context.Context, serviceIds ...string) ([]*API, error) GetInfo(ctx context.Context, aid string) (*Info, error) ListInfo(ctx context.Context, aids ...string) ([]*Info, error) ListInfoForService(ctx context.Context, serviceId string) ([]*Info, error) + ListInfoForServices(ctx context.Context, serviceIds ...string) ([]*Info, error) ListLatestCommitProxy(ctx context.Context, aid ...string) ([]*commit.Commit[Proxy], error) LatestProxy(ctx context.Context, aid string) (*commit.Commit[Proxy], error) diff --git a/service/strategy/iml.go b/service/strategy/iml.go index 5831d7a9..21a433cd 100644 --- a/service/strategy/iml.go +++ b/service/strategy/iml.go @@ -22,12 +22,26 @@ type imlStrategyService struct { universally.IServiceEdit[Edit] } -func (i *imlStrategyService) AllByScope(ctx context.Context, scope int, target string) ([]*Strategy, error) { +func (i *imlStrategyService) SearchAll(ctx context.Context, keyword string, driver string, scope int, target string) ([]*Strategy, error) { w := make(map[string]interface{}) w["scope"] = scope if target != "" { w["target"] = target } + list, err := i.store.Search(ctx, keyword, w, "update_at") + if err != nil { + return nil, err + } + return utils.SliceToSlice(list, FromEntity), nil +} + +func (i *imlStrategyService) AllByScope(ctx context.Context, driver string, scope int, target string) ([]*Strategy, error) { + w := make(map[string]interface{}) + w["scope"] = scope + if target != "" { + w["target"] = target + } + w["driver"] = driver list, err := i.store.List(ctx, w) if err != nil { return nil, err @@ -152,11 +166,13 @@ func createEntityHandler(i *Create) *strategy.Strategy { Desc: i.Desc, Filters: i.Filters, Config: i.Config, + Driver: i.Driver, Scope: i.Scope, Target: i.Target, CreateAt: now, UpdateAt: now, - IsStop: true, + IsStop: false, + IsDelete: false, } } func updateHandler(e *strategy.Strategy, i *Edit) { diff --git a/service/strategy/service.go b/service/strategy/service.go index 557aeb51..7dfe0b91 100644 --- a/service/strategy/service.go +++ b/service/strategy/service.go @@ -14,8 +14,9 @@ import ( type IStrategyService interface { universally.IServiceCreate[Create] universally.IServiceEdit[Edit] - AllByScope(ctx context.Context, scope int, target string) ([]*Strategy, error) + AllByScope(ctx context.Context, driver string, scope int, target string) ([]*Strategy, error) Search(ctx context.Context, keyword string, driver string, scope int, target string, page int, pageSize int, filters []string, order ...string) ([]*Strategy, int64, error) + SearchAll(ctx context.Context, keyword string, driver string, scope int, target string) ([]*Strategy, error) Get(ctx context.Context, id string) (*Strategy, error) SortDelete(ctx context.Context, id string) error Delete(ctx context.Context, id ...string) error diff --git a/stores/strategy/model.go b/stores/strategy/model.go index 7be1799a..c7287ef6 100644 --- a/stores/strategy/model.go +++ b/stores/strategy/model.go @@ -6,7 +6,7 @@ type Strategy struct { Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"` UUID string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"` Name string `gorm:"type:varchar(100);not null;column:name;comment:name"` - Priority int `gorm:"type:tinyint(4);not null;column:priority;comment:优先级"` + Priority int `gorm:"type:int(11);not null;column:priority;comment:优先级"` Desc string `gorm:"type:text;null;column:desc;comment:描述"` Filters string `gorm:"type:mediumtext;null;column:filters;comment:筛选条件"` Config string `gorm:"type:mediumtext;null;column:config;comment:配置"` diff --git a/strategy-filter/filter.go b/strategy-filter/filter.go new file mode 100644 index 00000000..0d74cd6e --- /dev/null +++ b/strategy-filter/filter.go @@ -0,0 +1,192 @@ +package strategy_filter + +import ( + "context" + "fmt" + "regexp" + "strings" + + "github.com/eolinker/eosc" + + strategy_dto "github.com/APIParkLab/APIPark/module/strategy/dto" +) + +var ( + filterHandler = NewHandler() +) + +type Option struct { + Name string + Title string + Type string + Pattern *regexp.Regexp + Options []string +} + +const ( + TypeRemote = "remote" + TypePattern = "pattern" + TypeStatic = "static" + + ValuesALL = "ALL" + + ScopeGlobal = "global" + ScopeTeam = "team" + ScopeService = "service" +) + +const ( + HttpALL = "ALL" +) + +func CheckFilters(name string, scope strategy_dto.Scope, filters []*strategy_dto.Filter) error { + fs, ok := filterHandler.Options(scope.String()) + if !ok { + return fmt.Errorf("unknown scope %s", scope) + } + filterNameSet := make(map[string]struct{}) + for _, filter := range filters { + op, ok := fs[filter.Name] + if !ok { + return fmt.Errorf("%s filter %s not found", name, filter.Name) + } + for _, value := range filter.Values { + if op.Pattern != nil && !op.Pattern.MatchString(value) { + return fmt.Errorf("%s filter %s value %s not match pattern", name, filter.Name, value) + } + } + + if len(filter.Values) == 0 { + return fmt.Errorf("%s.Options can't be null. filter.Name:%s ", name, filter.Name) + } + + if _, has := filterNameSet[filter.Name]; has { + return fmt.Errorf("%s.Name %s is reduplicative. ", name, filter.Name) + } + filterNameSet[filter.Name] = struct{}{} + } + + return nil +} + +type OptionTitle struct { + Field string `json:"field"` + Title string `json:"title" aoi18n:""` +} + +type IRemoteFilter interface { + IFilter + Titles() []OptionTitle + Key() string + Target() string + RemoteList(ctx context.Context, keyword string, condition map[string]interface{}, page int, pageSize int) ([]any, int64, error) +} + +type IFilter interface { + Name() string + Title() string + Labels(values ...string) []string + Type() string + Scopes() []string + Option() *Option +} + +type IFilterHandler interface { + RemoteFilter(name string) (IRemoteFilter, bool) + Options(scope string) (map[string]*Option, bool) + FilterLabel(name string, values []string) (*Info, error) +} + +type Info struct { + Title string + Label string +} + +type ScopeFilterOption eosc.Untyped[string, *Option] + +type Handler struct { + filters eosc.Untyped[string, IFilter] + remoteFilters eosc.Untyped[string, IRemoteFilter] + options eosc.Untyped[string, ScopeFilterOption] +} + +func NewHandler() *Handler { + return &Handler{ + filters: eosc.BuildUntyped[string, IFilter](), + remoteFilters: eosc.BuildUntyped[string, IRemoteFilter](), + options: eosc.BuildUntyped[string, ScopeFilterOption](), + } +} + +func (f *Handler) RemoteFilter(name string) (IRemoteFilter, bool) { + return f.remoteFilters.Get(name) +} + +func (f *Handler) RegisterRemoteFilter(filter IRemoteFilter) { + f.remoteFilters.Set(filter.Name(), filter) + f.setScopes(filter) +} + +func (f *Handler) RegisterFilter(filter IFilter) { + f.filters.Set(filter.Name(), filter) + f.setScopes(filter) +} + +func (f *Handler) setScopes(filter IFilter) { + for _, scope := range filter.Scopes() { + tmp, has := f.options.Get(scope) + if !has { + tmp = eosc.BuildUntyped[string, *Option]() + f.options.Set(scope, tmp) + } + tmp.Set(filter.Name(), filter.Option()) + } +} + +func (f *Handler) Options(scope string) (map[string]*Option, bool) { + options, has := f.options.Get(scope) + if !has { + return nil, false + } + return options.All(), true +} + +func (f *Handler) FilterLabel(name string, values []string) (*Info, error) { + if tmp, has := f.remoteFilters.Get(name); has { + return &Info{ + Title: tmp.Title(), + Label: strings.Join(tmp.Labels(values...), ","), + }, nil + } + if tmp, has := f.filters.Get(name); has { + return &Info{ + Title: tmp.Title(), + Label: strings.Join(tmp.Labels(values...), ","), + }, nil + } + return nil, fmt.Errorf("filter %s not found", name) +} + +func RegisterRemoteFilter(filter IRemoteFilter) { + filterHandler.RegisterRemoteFilter(filter) +} + +func RemoteFilter(name string) (IRemoteFilter, bool) { + return filterHandler.RemoteFilter(name) +} + +func Options(scope string) (map[string]*Option, bool) { + return filterHandler.Options(scope) +} + +func FilterLabel(name string, values []string) (*Info, error) { + return filterHandler.FilterLabel(name, values) +} + +func FilterGet(name string) (IFilter, bool) { + f, has := filterHandler.remoteFilters.Get(name) + if has { + return f, true + } + return filterHandler.filters.Get(name) +} diff --git a/strategy-filter/ip.go b/strategy-filter/ip.go new file mode 100644 index 00000000..7e1cc012 --- /dev/null +++ b/strategy-filter/ip.go @@ -0,0 +1,58 @@ +package strategy_filter + +import "regexp" + +var ( + CIDRIpv4Exp = `^(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([1-9]|[1-2]\d|3[0-2]))?$` +) + +var _ IFilter = &ipFilter{} + +func init() { + filterHandler.RegisterFilter(newIpFilter()) +} + +type ipFilter struct { + name string + title string + typ string + pattern *regexp.Regexp +} + +func newIpFilter() *ipFilter { + return &ipFilter{ + name: "ip", + title: "IP", + typ: TypePattern, + pattern: regexp.MustCompile(CIDRIpv4Exp), + } +} + +func (i *ipFilter) Name() string { + return i.name +} + +func (i *ipFilter) Title() string { + return i.title +} + +func (i *ipFilter) Labels(values ...string) []string { + return values +} + +func (i *ipFilter) Type() string { + return i.typ +} + +func (i *ipFilter) Scopes() []string { + return []string{ScopeGlobal, ScopeService} +} + +func (i *ipFilter) Option() *Option { + return &Option{ + Name: i.name, + Title: i.title, + Type: i.typ, + Pattern: i.pattern, + } +} diff --git a/strategy-filter/method.go b/strategy-filter/method.go new file mode 100644 index 00000000..f91156da --- /dev/null +++ b/strategy-filter/method.go @@ -0,0 +1,60 @@ +package strategy_filter + +import "net/http" + +var _ IFilter = &methodFilter{} + +func init() { + filterHandler.RegisterFilter(newMethodFilter()) +} + +type methodFilter struct { + name string + title string + typ string + options []string +} + +func newMethodFilter() *methodFilter { + return &methodFilter{ + name: "method", + title: "api method", + typ: TypeStatic, + options: []string{HttpALL, http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch, http.MethodHead, http.MethodOptions}, + } +} + +func (m *methodFilter) Name() string { + return m.name +} + +func (m *methodFilter) Title() string { + return m.title +} + +func (m *methodFilter) Labels(values ...string) []string { + if len(values) > 0 && values[0] != ValuesALL { + return []string{"all method"} + } + if len(values) == 0 { + return []string{"-"} + } + return values +} + +func (m *methodFilter) Type() string { + return m.typ +} + +func (m *methodFilter) Scopes() []string { + return []string{ScopeGlobal, ScopeService} +} + +func (m *methodFilter) Option() *Option { + return &Option{ + Name: m.name, + Title: m.title, + Type: m.typ, + Options: m.options, + } +} diff --git a/strategy-filter/path.go b/strategy-filter/path.go new file mode 100644 index 00000000..3f189f05 --- /dev/null +++ b/strategy-filter/path.go @@ -0,0 +1,58 @@ +package strategy_filter + +import "regexp" + +var _ IFilter = &pathFilter{} + +var ( + ApiPathRegexp = `^\*?[\w-/]+\*?$` +) + +type pathFilter struct { + name string + title string + typ string + pattern *regexp.Regexp +} + +func init() { + filterHandler.RegisterFilter(newPathFilter()) +} + +func newPathFilter() *pathFilter { + return &pathFilter{ + name: "path", + title: "api path", + typ: TypePattern, + pattern: regexp.MustCompile(ApiPathRegexp), + } +} + +func (p *pathFilter) Name() string { + return p.name +} + +func (p *pathFilter) Title() string { + return p.title +} + +func (p *pathFilter) Labels(values ...string) []string { + return values +} + +func (p *pathFilter) Type() string { + return p.typ +} + +func (p *pathFilter) Scopes() []string { + return []string{ScopeGlobal, ScopeService} +} + +func (p *pathFilter) Option() *Option { + return &Option{ + Name: p.name, + Title: p.title, + Type: p.typ, + Pattern: p.pattern, + } +}