1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690 |
- package service
- import (
- "fmt"
- "math"
- "net/url"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
- "go-admin/app/observe/models"
- "go-admin/app/observe/models/query"
- "go-admin/app/observe/service/dto"
- cDto "go-admin/common/dto"
- "go-admin/common/prometheus"
- "go-admin/config"
- "go-admin/utils"
- "github.com/pkg/errors"
- "golang.org/x/text/cases"
- "golang.org/x/text/language"
- "gorm.io/gorm"
- )
- type UrlMapping struct {
- // service.Service
- utils.OtService
- }
- // GetPage 获取UrlMapping列表
- func (e *UrlMapping) GetPage(req *dto.UrlMappingGetPageReq, resp *[]dto.UrlMappingListResp, count *int64) error {
- req.CheckFilling(time.Minute * 5)
- urlMappings := []models.UrlMapping{}
- level := 1
- if req.Level != "" {
- var err error
- if level, err = strconv.Atoi(req.Level); err != nil {
- level = 1
- }
- }
- appAlias := ""
- e.Orm.Model(&models.App{}).Where("id", req.AppId).Pluck("alias", &appAlias)
- if appAlias == "" {
- return errors.New(fmt.Sprintf("未查询到应用别名: %s", req.AppId))
- }
- req.Level = strconv.Itoa(level)
- db := e.Orm.Model(&models.UrlMapping{}).
- Where("app_id", req.AppId).
- Where("level=? OR (level<? AND is_perfect_match=1)", level, level).
- Scopes(cDto.Paginate(req.GetPageSize(), req.GetPageIndex()))
- if req.ServiceName != "" {
- db.Where("service_name", req.ServiceName)
- }
- if req.Url != "" {
- db.Where("url LIKE ?", fmt.Sprintf("%%%s%%", req.Url))
- }
- err := db.Find(&urlMappings).Limit(-1).Offset(-1).Count(count).Error
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- return nil
- }
- return errors.Wrap(err, "获取收藏的业务映射失败")
- }
- svcMap, err := query.NewService().ServiceMap(appAlias)
- if err != nil {
- return err
- }
- wg := sync.WaitGroup{}
- *resp = make([]dto.UrlMappingListResp, len(urlMappings))
- for i, um := range urlMappings {
- (*resp)[i] = dto.UrlMappingListResp{
- Id: int(um.ID),
- AppId: strconv.Itoa(int(um.AppId)),
- AppAlias: um.AppAlias,
- Name: um.Name,
- Method: um.Method,
- Url: um.Url,
- Level: strconv.Itoa(int(um.Level)),
- IsPerfectMatch: strconv.Itoa(int(um.IsPerfectMatch)),
- Type: strconv.Itoa(int(um.Type)),
- Module: um.Module,
- Summary: um.Summary,
- Favor: strconv.Itoa(int(um.Favor)),
- ServiceName: um.ServiceName,
- ServiceNameCN: svcMap[um.ServiceName],
- }
- wg.Add(1)
- if config.ExtConfig.ClickhouseMetrics {
- go e.urlMappingStatsFromClickhouse(&wg, req, &((*resp)[i]))
- } else {
- go e.urlMappingStats(&wg, req, &((*resp)[i]))
- }
- }
- wg.Wait()
- // return e.genUrlMappingListResp(urlMappings, req, resp)
- return nil
- }
- func (s *UrlMapping) urlMappingStatsFromClickhouse(wg *sync.WaitGroup, req *dto.UrlMappingGetPageReq, item *dto.UrlMappingListResp) {
- defer wg.Done()
- urlPrefix := fmt.Sprintf("%s%%", item.Url)
- db := s.ChOrm.Model(&models.TracesURL{}).
- Where("AppAlias=? and ServiceName=? and Method=? and Route like ?", item.AppAlias, item.ServiceName, item.Method, urlPrefix).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime)
- fields := []string{
- "count() as Total",
- "max(Duration) as MaxDuration",
- "avg(Duration) as AvgDuration",
- "sum(if(StatusCode>=400, 1, 0)) as ErrorNum",
- "quantile(0.5)(Duration) as P50Duration",
- "quantile(0.90)(Duration) as P90Duration",
- "quantile(0.99)(Duration) as P99Duration",
- }
- row := struct {
- Total int64
- MaxDuration int64
- ErrorNum int64
- AvgDuration float64
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }{}
- err := db.Select(fields).Find(&row).Error
- if err != nil {
- return
- }
- if row.Total == 0 {
- item.UrlMappingBaseStats = dto.UrlMappingBaseStats{}
- item.DurationStats = dto.DurationStats{
- Time: []string{},
- P50: []float64{},
- P90: []float64{},
- P99: []float64{},
- }
- return
- }
- mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
- timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- if req.EndTime-req.StartTime > 60*60 {
- timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- }
- fields = []string{
- timeField,
- "quantile(0.5)(Duration) as P50Duration",
- "quantile(0.90)(Duration) as P90Duration",
- "quantile(0.99)(Duration) as P99Duration",
- }
- quantiles := []struct {
- StartTime string
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }{}
- if err := db.Select(fields).Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
- return
- }
- item.Total = row.Total
- item.ErrorNum = row.ErrorNum
- item.ErrorRate = float64(row.ErrorNum) / float64(row.Total)
- item.Rpm = math.Round(float64(row.Total)/mins*100) / 100
- item.Max = math.Round(float64(row.MaxDuration)/1e6*100) / 100
- item.Avg = math.Round(float64(row.AvgDuration)/1e6*100) / 100
- item.P50 = math.Round(float64(row.P50Duration)/1e6*100) / 100
- item.P90 = math.Round(float64(row.P90Duration)/1e6*100) / 100
- item.P99 = math.Round(float64(row.P99Duration)/1e6*100) / 100
- item.DurationStats = dto.DurationStats{
- Time: make([]string, len(quantiles)),
- P50: make([]float64, len(quantiles)),
- P90: make([]float64, len(quantiles)),
- P99: make([]float64, len(quantiles)),
- }
- for i, quantile := range quantiles {
- item.DurationStats.Time[i] = quantile.StartTime
- item.DurationStats.P50[i] = math.Round(quantile.P50Duration/1e6*100) / 100
- item.DurationStats.P90[i] = math.Round(quantile.P90Duration/1e6*100) / 100
- item.DurationStats.P99[i] = math.Round(quantile.P99Duration/1e6*100) / 100
- }
- }
- func (s *UrlMapping) urlMappingStats(wg *sync.WaitGroup, req *dto.UrlMappingGetPageReq, item *dto.UrlMappingListResp) {
- defer wg.Done()
- wg2 := sync.WaitGroup{}
- metric := "observe_server_duration_milliseconds"
- labels := map[string]string{
- "url_level": item.Level,
- "url_is_perfect_match": item.IsPerfectMatch,
- "url_method": item.Method,
- "url_prefix": item.Url,
- "service_name": item.ServiceName,
- "app_alias": item.AppAlias,
- }
- ts := time.Unix(req.EndTime, 0)
- mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
- hist := prometheus.NewHistogram(&wg2, metric, labels, ts, int64(mins))
- var total int64
- var avg, max, p50, p90, p99, errRate float64
- times := []string{}
- p50s, p90s, p99s := []float64{}, []float64{}, []float64{}
- wg2.Add(10)
- go hist.Total(&total)
- go hist.Avg(&avg)
- go hist.ErrorRate(&errRate)
- go hist.Quantile(0.5, &p50)
- go hist.Quantile(0.9, &p90)
- go hist.Quantile(0.99, &p99)
- go hist.Quantile(0.999, &max) // 如果使用1, 会读到桶边界
- go hist.QuantileMinutes(0.5, &[]string{}, &p50s)
- go hist.QuantileMinutes(0.9, &[]string{}, &p90s)
- go hist.QuantileMinutes(0.99, ×, &p99s)
- wg2.Wait()
- item.Max = math.Round(max*100) / 100
- item.Avg = math.Round(avg*100) / 100
- item.Total = total
- item.ErrorRate = math.Round(errRate*100) / 100
- item.ErrorNum = int64(float64(total) * errRate)
- item.Rpm = math.Round(float64(total)/mins*100) / 100
- item.P50 = math.Round(p50*100) / 100
- item.P90 = math.Round(p90*100) / 100
- item.P99 = math.Round(p99*100) / 100
- item.DurationStats = dto.DurationStats{
- P50: p50s,
- P90: p90s,
- P99: p99s,
- Time: times,
- }
- }
- func (e *UrlMapping) GetFavors(req *dto.UrlMappingGetFavorsReq, resp *[]dto.UrlMappingListResp) error {
- urlMappings := []models.UrlMapping{}
- err := e.Orm.Model(&models.UrlMapping{}).Where("app_id=? AND favor=?", req.AppId, 1).Limit(5).Find(&urlMappings).Error
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- return nil
- }
- return errors.Wrap(err, "获取收藏的业务映射失败")
- }
- return e.genUrlMappingListResp(urlMappings, req, resp)
- }
- func (e *UrlMapping) genUrlMappingListResp(
- urlMappings []models.UrlMapping,
- a any,
- resp *[]dto.UrlMappingListResp) error {
- *resp = make([]dto.UrlMappingListResp, len(urlMappings))
- kinds := make([]string, len(urlMappings))
- for i, um := range urlMappings {
- kinds[i] = um.Url
- (*resp)[i] = dto.UrlMappingListResp{
- Id: int(um.ID),
- AppId: strconv.Itoa(int(um.AppId)),
- Name: um.Name,
- Method: um.Method,
- Url: um.Url,
- Type: strconv.Itoa(int(um.Type)),
- Module: um.Module,
- Summary: um.Summary,
- Favor: strconv.Itoa(int(um.Favor)),
- ServiceName: um.ServiceName,
- }
- }
- var err error
- req := new(dto.UrlMappingGetFavorsReq)
- switch r := a.(type) {
- case *dto.UrlMappingGetFavorsReq:
- req = r
- case *dto.UrlMappingGetPageReq:
- req = &dto.UrlMappingGetFavorsReq{
- AppId: r.AppId,
- TimeRange: models.TimeRange{
- StartTime: r.StartTime,
- EndTime: r.EndTime,
- },
- }
- default:
- return errors.Errorf("参数req非法")
- }
- baseStats := []dto.UrlMappingBaseStats{}
- baseStatsMap := map[string]dto.UrlMappingBaseStats{}
- err = e.ChOrm.Debug().Model(&models.TracesURL{}).
- Select([]string{
- "Kind",
- "COUNT() AS Total",
- "SUM(IF(StatusCode>=400, 1, 0)) AS ErrorNum",
- "round(MAX(Duration)/1e6, 2) AS Max",
- "round(MIN(Duration)/1e6, 2) AS Min",
- "round(AVG(Duration)/1e6, 2) AS Avg",
- }).Where("AppId", req.AppId).Where("Kind IN ?", kinds).
- Where("Timestamp>=toDateTime(?) AND Timestamp<toDateTime(?)", req.StartTime, req.EndTime).
- Group("Kind").Find(&baseStats).Error
- if err != nil && err != gorm.ErrRecordNotFound {
- return errors.Wrap(err, "基础统计数据获取失败")
- }
- for _, bs := range baseStats {
- baseStatsMap[bs.Kind] = bs
- }
- // 获取metrics相关数据
- type ds struct {
- Kind string
- StartTimeUnix time.Time
- TimeUnix time.Time
- Count int64
- Sum float64
- BucketCounts []uint64
- ExplicitBounds []float64
- Min float64
- Max float64
- }
- data := []ds{}
- db := e.ChOrm.Debug().Select("Attributes['url.kind'] AS Kind, StartTimeUnix, TimeUnix, Count, Sum, BucketCounts, ExplicitBounds, Min, Max").
- Table(models.TableNameMetricsHistogram).Where("Attributes['app.id']=?", req.AppId).Where("Kind IN ?", kinds)
- db.Where("TimeUnix>=? AND TimeUnix<?", req.StartTime, req.EndTime)
- if err := db.Order("Kind ASC, TimeUnix ASC").Find(&data).Error; err != nil {
- return errors.Wrap(err, "获取统计数据失败")
- }
- e.Log.Info("Get Page Service Clickhouse Metrics.")
- urlMap := map[string]dto.UrlMappingBaseStats{}
- statMap := map[string][]dto.DurationStat{}
- for _, item := range data {
- qv := Quantiles(item.Count, item.BucketCounts, item.ExplicitBounds, item.Max, []float64{0.5, 0.9, 0.99})
- base := dto.UrlMappingBaseStats{ // 由于已经按Timestamp排序,最后一个即为最后的结果
- Kind: item.Kind,
- P50: qv[0],
- P90: qv[1],
- P99: qv[2],
- }
- urlMap[item.Kind] = base
- if _, ok := statMap[item.Kind]; !ok {
- statMap[item.Kind] = []dto.DurationStat{}
- }
- statMap[item.Kind] = append(statMap[item.Kind], dto.DurationStat{
- Time: item.TimeUnix.Format("2006-01-02 15:04"),
- P50: qv[0],
- P90: qv[1],
- P99: qv[2],
- })
- }
- mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
- for kind, item := range urlMap {
- if st, ok := baseStatsMap[kind]; ok {
- item.Total = st.Total
- item.Max = st.Max
- item.Min = st.Min
- item.Avg = st.Avg
- item.ErrorNum = st.ErrorNum
- item.ErrorRate = st.ErrorRate
- item.Rpm = float64(item.Total) / mins
- urlMap[kind] = item
- }
- }
- // 整合数据
- for i := range *resp {
- kind := (*resp)[i].Url
- baseStat := urlMap[kind]
- (*resp)[i].UrlMappingBaseStats = baseStat
- (*resp)[i].DurationStats = dto.DurationStats{}
- for _, stat := range statMap[kind] {
- (*resp)[i].DurationStats.Time = append((*resp)[i].DurationStats.Time, stat.Time)
- (*resp)[i].DurationStats.P50 = append((*resp)[i].DurationStats.P50, stat.P50)
- (*resp)[i].DurationStats.P90 = append((*resp)[i].DurationStats.P90, stat.P90)
- (*resp)[i].DurationStats.P99 = append((*resp)[i].DurationStats.P99, stat.P99)
- }
- }
- return nil
- }
- func Quantiles(total int64, bucketCounts []uint64, explicitBounds []float64, max float64, qs []float64) (qv []float64) {
- qt := make([]float64, len(qs))
- qv = make([]float64, len(qs))
- for i := range qs {
- qt[i] = float64(total) * qs[i]
- }
- sum := float64(0)
- for i := 1; i < len(bucketCounts); i++ { // bucketCounts第0个值代表(-Inf, 0],此值永远取不到
- cnt := float64(bucketCounts[i])
- prevSum := sum
- sum += cnt
- for j, n := range qt {
- if qv[j] == 0 && sum >= n {
- if i < len(explicitBounds) {
- qv[j] = explicitBounds[i-1] + (explicitBounds[i]-explicitBounds[i-1])*(n-prevSum)/(sum-prevSum)
- } else {
- qv[j] = explicitBounds[len(explicitBounds)-1] + (max-explicitBounds[len(explicitBounds)-1])*(n-prevSum)/(sum-prevSum)
- }
- }
- }
- }
- return
- }
- // Get 获取UrlMapping对象
- func (e *UrlMapping) Get(c *dto.UrlMappingGetReq, result *dto.UrlMappingGetResp) error {
- var data models.UrlMapping
- model := new(models.UrlMapping)
- err := e.Orm.Model(&data).
- First(model, c.GetId()).Error
- if err != nil {
- e.Log.Errorf("db error:%s", err)
- return err
- }
- *result = dto.UrlMappingGetResp{
- // Id: model.Id,
- AppId: strconv.Itoa(int(model.AppId)),
- Name: model.Name,
- Url: model.Url,
- Type: strconv.Itoa(int(model.Type)),
- Module: model.Module,
- Summary: model.Summary,
- Favor: strconv.Itoa(int(model.Favor)),
- }
- return nil
- }
- // Insert 创建UrlMapping对象
- func (e *UrlMapping) Insert(c *dto.UrlMappingInsertReq) error {
- var err error
- var data models.UrlMapping
- cnt := int64(0)
- err = e.Orm.Model(&models.UrlMapping{}).Where("app_id", c.AppId).Where("url", c.Url).Count(&cnt).Error
- if err != nil {
- e.Log.Errorf("查询app_id:%d下url:%s的个数失败:%s", c.AppId, c.Url, err)
- return err
- }
- if cnt > 0 {
- e.Log.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
- return fmt.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
- }
- c.Generate(&data)
- err = e.Orm.Create(&data).Error
- if err != nil {
- e.Log.Errorf("创建url-mapping失败:%s", err)
- return err
- }
- return nil
- }
- // Update 修改UrlMapping对象
- func (e *UrlMapping) Update(c *dto.UrlMappingUpdateReq) error {
- var err error
- var data = models.UrlMapping{}
- err = e.Orm.First(&data, c.GetId()).Error
- if err != nil {
- if err == gorm.ErrRecordNotFound {
- return fmt.Errorf("更新目标不存在, id: %d", c.GetId())
- }
- return err
- }
- c.Generate(&data)
- // 去掉以下逻辑,因为app_id+url不再唯一
- // var cnt int64
- // err = e.Orm.Model(&models.UrlMapping{}).
- // Where("id != ? AND app_id = ? AND url = ?", c.GetId(), c.AppId, c.Url).Count(&cnt).Error
- // if err != nil {
- // e.Log.Errorf("查询app_id:%d下url:%s的个数失败:%s", c.AppId, c.Url, err)
- // return err
- // }
- // if cnt > 0 {
- // e.Log.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
- // return fmt.Errorf("该app_id:%s下已经存在相同的url:%s", c.AppId, c.Url)
- // }
- db := e.Orm.Save(&data)
- if err = db.Error; err != nil {
- e.Log.Errorf("UrlMappingService Save error:%s \r\n", err)
- return err
- }
- return nil
- }
- // Favor 收藏/取消收藏
- func (e *UrlMapping) Favor(c *dto.UrlMappingFavorReq) error {
- var data = models.UrlMapping{}
- if err := e.Orm.First(&data, c.Id).Error; err != nil {
- return errors.Wrap(err, "获取urlmapping数据失败")
- }
- // 不需要再有5个的限制了
- // if c.Favor == "1" {
- // var count int64
- // err = e.Orm.Model(&models.UrlMapping{}).Where("app_id", data.AppId).Where("favor", 1).Count(&count).Error
- // if err != nil {
- // return errors.Wrap(err, "获取收藏数量失败")
- // }
- // if count >= 5 {
- // return errors.New("最多只能收藏5个业务映射")
- // }
- // }
- if err := e.Orm.Model(&data).Update("favor", c.Favor).Error; err != nil {
- msg := "收藏失败"
- if c.Favor == "0" {
- msg = "取消" + msg
- }
- return errors.Wrap(err, msg)
- }
- return nil
- }
- // Remove 删除UrlMapping
- func (e *UrlMapping) Remove(d *dto.UrlMappingDeleteReq) error {
- var data models.UrlMapping
- db := e.Orm.Model(&data).Delete(&data, d.GetIds())
- if err := db.Error; err != nil {
- e.Log.Errorf("Service RemoveUrlMapping error:%s \r\n", err)
- return err
- }
- return nil
- }
- // GetDetail 获取指定业务映射指定区间内的全部请求
- func (e *UrlMapping) GetDetail(c *dto.UrlMappingGetDetailReq, result *[]dto.UrlMappingGetDetailResp, count *int64) (err error) {
- // var data models.UrlMapping
- // err = e.Orm.Model(&models.UrlMapping{}).First(&data, c.Id).Count(count).Error
- // if err != nil {
- // msg := "获取业务映射失败"
- // e.Log.Error(fmt.Errorf("%s, id: %d, err: %s", msg, c.Id, err))
- // return fmt.Errorf(msg)
- // }
- appAlias := ""
- if err := e.Orm.Model(&models.App{}).Where("id", c.AppId).Pluck("alias", &appAlias).Error; err != nil {
- return errors.Wrap(err, "获取app别名失败")
- }
- traceUrls := []models.TracesURL{}
- if c.Route == "" {
- c.Route = c.Kind // 兼容旧逻辑
- }
- db := e.ChOrm.Debug().Model(&models.TracesURL{}).
- Scopes(cDto.Paginate(c.GetPageSize(), c.GetPageIndex())).
- Where("AppAlias", appAlias).Where("Route", c.Route)
- if c.Method != "" {
- db.Where("Method", c.Method)
- }
- if c.ServiceName != "" {
- db.Where("ServiceName", c.ServiceName)
- }
- if c.OnlyError == "1" || c.OnlyException { // only error 后面删除
- db.Where("StatusCode>=?", 400)
- }
- if c.MinDuration > 0 {
- db.Where("Duration>=?", c.MinDuration*float64(time.Millisecond))
- }
- if c.MaxDuration > 0 {
- db.Where("Duration<=?", c.MaxDuration*float64(time.Millisecond))
- }
- if c.StartTime == c.EndTime && c.StartTime > 0 { // 对于start_time == end_time的情况,取前后一分钟的数据
- db.Where("Timestamp>=toDateTime(?) AND Timestamp<=toDateTime(?)", c.StartTime-60, c.EndTime+60)
- } else {
- c.CheckFilling(time.Hour * 48) // 最大取2天,加快速度
- db.Where("Timestamp>=toDateTime(?) AND Timestamp<=toDateTime(?)", c.StartTime, c.EndTime)
- }
- c.SortInfo.Field = cases.Title(language.Und).String(c.SortInfo.Field)
- db.Order(c.SortInfo.OrderBy([]string{"Timestamp", "Duration"}, "Duration", "DESC"))
- err = db.Find(&traceUrls).Limit(-1).Offset(-1).Count(count).Error
- if err != nil && err != gorm.ErrRecordNotFound {
- return errors.Wrap(err, "获取业务映射详情失败")
- }
- *result = make([]dto.UrlMappingGetDetailResp, len(traceUrls))
- // spanIds := make([]string, len(*result))
- for i, item := range traceUrls {
- // spanIds[i] = item.SpanID
- item.Duration /= 1e6
- (*result)[i] = dto.UrlMappingGetDetailResp{
- Datetime: item.Timestamp.Local().Format(time.DateTime),
- Timestamp: item.Timestamp.Unix(),
- Route: item.Route,
- Target: item.Target,
- URL: item.URL,
- Flavor: item.Flavor,
- Host: item.Host,
- Method: item.Method,
- StatusCode: item.StatusCode,
- Message: item.Message,
- TraceID: item.TraceID,
- SpanID: item.SpanID,
- ServiceName: item.ServiceName,
- Duration: item.Duration,
- }
- }
- // spanInfos := []models.Trace{}
- // err = e.ChOrm.Table(models.TableNameTrace).Where("SpanId IN ?", spanIds).Find(&spanInfos).Error
- // if err != nil {
- // msg := "获取业务映射对应的span信息失败"
- // e.Log.Errorf("%s: %s", msg, err)
- // return fmt.Errorf(msg)
- // }
- // spanInfoMap := make(map[string]models.Trace, len(spanInfos))
- // for _, span := range spanInfos {
- // spanInfoMap[span.SpanID] = span
- // }
- // for i, item := range *result {
- // if spanInfo, ok := spanInfoMap[item.SpanID]; ok { // otel_traces 表中的数据不全,有的spanId找不到
- // (*result)[i].SpanDetail = &spanInfo
- // }
- // }
- return
- }
- // Sankey 桑基图
- func (e *UrlMapping) Sankey(c *dto.UrlMappingSankeyReq, result *dto.UrlMappingSankeyResp) (err error) {
- links1 := []dto.SankeyLink{}
- c.CheckFilling(time.Minute * 5)
- var alias string
- if err := e.Orm.Model(&models.App{}).Where("id", c.AppId).Pluck("alias", &alias).Error; err != nil {
- return errors.Wrap(err, "appid查找失败")
- }
- var model models.TracesURL
- scope := func(db *gorm.DB) *gorm.DB {
- db.Where("AppAlias", alias)
- db.Where("Timestamp >= ? AND Timestamp < ?", c.StartTime, c.EndTime)
- return db
- }
- if c.Percentile == 0 { // 默认百分位数 90%
- c.Percentile = 0.9
- }
- // 获取指定分位数对应的duration
- var percentile float64 = 0
- fieldsTmpl := "quantile(%.2f)(Duration) AS Q"
- err = e.ChOrm.Model(&models.TracesURL{}).Select(fmt.Sprintf(fieldsTmpl, c.Percentile)).Scopes(scope).Pluck("Q", &percentile).Error
- if err != nil {
- return errors.Wrap(err, "获取时延分位数失败")
- }
- pcond := func(db *gorm.DB) *gorm.DB {
- db.Where("Duration>?", percentile)
- return db
- }
- if err = e.ChOrm.Debug().Model(&models.TracesURL{}).
- // anyLast取最后遇到的值,结果可能不确定,在AppName频繁变化的情况下,可能会取到不同的值
- Select("anyLast(AppName) AS Source, CONCAT('Name:', Name) AS Target, COUNT() AS Value").
- Scopes(scope, pcond).
- Group("Target").
- Find(&links1).Error; err != nil {
- return errors.Wrap(err, "获取links数据失败1")
- }
- links2 := []dto.SankeyLink{}
- if err = e.ChOrm.Model(&model).
- Select("CONCAT('Name:', Name) AS Source, CONCAT('Route:', Route) AS Target, COUNT() AS Value").
- Scopes(scope, pcond).
- Group("Source, Target").
- Find(&links2).Error; err != nil {
- return errors.Wrap(err, "获取links数据失败2")
- }
- // links3 := []dto.SankeyLink{}
- // t1 := e.ChOrm.Model(&model).
- // Select("CONCAT('Route:', Route) AS Source, CONCAT('Target:', Target) AS Target, COUNT() AS Value").
- // Scopes(scope).Group("Source, Target")
- // t2 := e.ChOrm.Table("(?) AS t1", t1).
- // Select("Source, Target, Value, ROW_NUMBER() OVER(PARTITION BY Source ORDER BY Value DESC) AS RowNum")
- // if err = e.ChOrm.Table("(?) AS t2", t2).
- // Select("Source, Target, Value").
- // Where("RowNum<=?", 10). // 暂定最多只取前10条
- // Find(&links3).Error; err != nil {
- // return errors.Wrap(err, "获取links数据失败3")
- // }
- links4mp := []map[string]any{}
- if err = e.ChOrm.Model(&model).
- // 暂定每500毫秒一个区间
- // Select("CONCAT('Target:', `Target`) AS `Source`, CEIL(Duration/1e6/500)*500 AS Target2, COUNT() Value").
- Select("CONCAT('Route:', `Route`) AS `Source`, CEIL(Duration/1e6/500)*500 AS Target, COUNT() Value").
- Scopes(scope, pcond).
- // Group("Source, Target2").
- // Order("Source, Target2").
- Group("Source, Target").
- Order("Source, Target").
- Find(&links4mp).Error; err != nil {
- return errors.Wrap(err, "获取links数据失败4")
- }
- links4 := make([]dto.SankeyLink, len(links4mp))
- var prevTarget, curTarget string
- var prevSource, curSource string
- var k int
- for i, link := range links4mp {
- curSource = link["Source"].(string)
- // curTarget = strconv.Itoa(int(link["Target2"].(float64)))
- curTarget = link["Target"].(string)
- target := ""
- if prevSource != curSource {
- k = 0
- }
- if k == 0 {
- target = fmt.Sprintf("%sms以下", curTarget)
- prevTarget = curTarget
- } else if k > 9 {
- target = fmt.Sprintf("%sms以上", prevTarget)
- } else {
- target = fmt.Sprintf("%s~%sms", prevTarget, curTarget)
- prevTarget = curTarget
- }
- prevSource = curSource
- k++
- cur := dto.SankeyLink{
- Source: link["Source"].(string),
- Target: target,
- Value: int(link["Value"].(uint64)),
- }
- links4[i] = cur
- }
- // links := make([]dto.SankeyLink, 0, len(links1)+len(links2)+len(links3)+len(links4))
- links := make([]dto.SankeyLink, 0, len(links1)+len(links2)+len(links4))
- links = append(links, links1...)
- links = append(links, links2...)
- // links = append(links, links3...)
- links = append(links, links4...)
- if len(links) == 0 {
- result.Links = []dto.SankeyLink{}
- result.Nodes = []dto.SankeyNode{}
- return nil
- }
- result.Links = links
- nodeNameMap := map[string]struct{}{
- links[0].Source: {},
- }
- for _, link := range links {
- nodeNameMap[link.Target] = struct{}{}
- }
- nodes := make([]dto.SankeyNode, 0, len(nodeNameMap))
- for name := range nodeNameMap {
- nodes = append(nodes, dto.SankeyNode{
- Name: name,
- })
- }
- result.Nodes = nodes
- return nil
- }
- func (s *UrlMapping) QualityFromClickhouse(req dto.UrlMappingQualityReq, resp *dto.UrlMappingQualityResp) error {
- req.CheckFilling(time.Hour)
- type BucketCount struct {
- Bucket string
- Total int64
- }
- appAlias := ""
- if err := s.Orm.Model(&models.App{}).Where("id", req.AppId).Pluck("alias", &appAlias).Error; err != nil {
- return errors.Wrap(err, "获取应用别名失败")
- }
- genBucketCounts := func(start, end int64) ([]BucketCount, error) {
- bucketCounts := []BucketCount{}
- db := s.ChOrm.Model(&models.TracesURL{}).
- Where("AppAlias=?", appAlias).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", start, end)
- err := db.Select(`IF(Duration/1e6<=5, '5', IF(Duration/1e6<=10, '10', IF(Duration/1e6<=25, '25', IF(Duration/1e6<=50, '50', IF(Duration/1e6<=75, '75',
- IF(Duration/1e6<=100, '100', IF(Duration/1e6<=250, '250', IF(Duration/1e6<=500, '500', IF(Duration/1e6<=750, '750', IF(Duration/1e6<=1000, '1000',
- IF(Duration/1e6<=2500, '2500', IF(Duration/1e6<=5000, '5000', IF(Duration/1e6<=7500, '7500', IF(Duration/1e6<=10000, '10000', '10000+'))))
- ))))) ))))) as Bucket, Count() as Total`).Group("Bucket").Order("Bucket asc").Find(&bucketCounts).Error
- if err != nil {
- return bucketCounts, errors.Wrap(err, "获取buckets数据失败")
- }
- sort.Slice(bucketCounts, func(i, j int) bool {
- bi, err := strconv.Atoi(bucketCounts[i].Bucket)
- if err != nil {
- return false
- }
- bj, err := strconv.Atoi(bucketCounts[j].Bucket)
- if err != nil {
- return true
- }
- return bi < bj
- })
- return bucketCounts, nil
- }
- type PxxDuration struct {
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }
- genQuantile := func(start, end int64) (PxxDuration, error) {
- row := struct {
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }{}
- db := s.ChOrm.Model(&models.TracesURL{}).
- Where("AppAlias=?", appAlias).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", start, end)
- err := db.Select([]string{
- "quantile(0.5)(Duration)/1e6 as P50Duration",
- "quantile(0.90)(Duration)/1e6 as P90Duration",
- "quantile(0.99)(Duration)/1e6 as P99Duration",
- }).Find(&row).Error
- return row, err
- }
- secs := req.EndTime - req.StartTime
- bc1, err := genBucketCounts(req.StartTime, req.EndTime)
- if err != nil {
- return err
- }
- bc2, err := genBucketCounts(req.StartTime-secs, req.EndTime-secs)
- if err != nil {
- return err
- }
- resp.Buckets = make([]string, 0, len(bc1))
- counts1, counts2 := []float64{}, []float64{}
- i, j := 0, 0
- for i < len(bc1) && j < len(bc2) {
- if bc1[i].Bucket == bc2[i].Bucket {
- resp.Buckets = append(resp.Buckets, bc1[i].Bucket)
- counts1 = append(counts1, float64(bc1[i].Total))
- counts2 = append(counts2, float64(bc2[j].Total))
- i++
- j++
- } else if bc1[i].Bucket < bc2[j].Bucket {
- resp.Buckets = append(resp.Buckets, bc1[i].Bucket)
- counts1 = append(counts1, float64(bc1[i].Total))
- i++
- } else {
- resp.Buckets = append(resp.Buckets, bc2[j].Bucket)
- counts2 = append(counts2, float64(bc2[j].Total))
- j++
- }
- }
- for i < len(bc1) {
- resp.Buckets = append(resp.Buckets, bc1[i].Bucket)
- i++
- }
- for j < len(bc2) {
- resp.Buckets = append(resp.Buckets, bc2[j].Bucket)
- j++
- }
- buckets := []float64{}
- for _, bucket := range resp.Buckets {
- if num, err := strconv.Atoi(bucket); err == nil {
- buckets = append(buckets, float64(num))
- }
- }
- pxx1, err := genQuantile(req.StartTime, req.EndTime)
- if err != nil {
- return nil
- }
- pxx2, err := genQuantile(req.StartTime-secs, req.EndTime-secs)
- if err != nil {
- return nil
- }
- resp.Current = dto.CountsQuantileIndexes{
- Counts: counts1,
- QuantileIndexes: map[string]int{
- "p50": sort.SearchFloat64s(buckets, pxx1.P50Duration),
- "p90": sort.SearchFloat64s(buckets, pxx1.P90Duration),
- "p99": sort.SearchFloat64s(buckets, pxx1.P99Duration),
- },
- }
- resp.Previous = dto.CountsQuantileIndexes{
- Counts: counts2,
- QuantileIndexes: map[string]int{
- "p50": sort.SearchFloat64s(buckets, pxx2.P50Duration),
- "p90": sort.SearchFloat64s(buckets, pxx2.P90Duration),
- "p99": sort.SearchFloat64s(buckets, pxx2.P99Duration),
- },
- }
- return nil
- }
- // 接口质量统计,即请求时长相关分布
- func (s *UrlMapping) Quality(req dto.UrlMappingQualityReq, resp *dto.UrlMappingQualityResp) error {
- wg := sync.WaitGroup{}
- req.CheckFilling(time.Hour)
- cur, prev := time.Unix(req.EndTime, 0), time.Unix(req.StartTime, 0)
- mins := int64(cur.Sub(prev).Minutes())
- name := "observe_server_duration_milliseconds"
- labels := map[string]string{"app_id": req.AppId}
- if req.UrlType != "" {
- labels["url_type"] = req.UrlType
- }
- buckets, counts := []float64{}, []float64{}
- var p50, p90, p99 [2]float64
- buckets2, counts2 := []float64{}, []float64{}
- wg.Add(8)
- hist := prometheus.NewHistogram(&wg, name, labels, cur, mins)
- hist2 := prometheus.NewHistogram(&wg, name, labels, prev, mins)
- go hist.BucketCounts(&buckets, &counts)
- go hist.Quantile(0.5, &p50[0])
- go hist.Quantile(0.9, &p90[0])
- go hist.Quantile(0.99, &p99[0])
- go hist2.BucketCounts(&buckets2, &counts2)
- go hist2.Quantile(0.5, &p50[1])
- go hist2.Quantile(0.9, &p90[1])
- go hist2.Quantile(0.99, &p99[1])
- wg.Wait()
- resp.Buckets = make([]string, len(buckets))
- for i, bucket := range buckets {
- resp.Buckets[i] = fmt.Sprintf("%.0f", bucket)
- }
- resp.Current = dto.CountsQuantileIndexes{
- Counts: counts,
- QuantileIndexes: map[string]int{
- "p50": sort.SearchFloat64s(buckets, p50[0]),
- "p90": sort.SearchFloat64s(buckets, p90[0]),
- "p99": sort.SearchFloat64s(buckets, p99[0]),
- },
- }
- resp.Previous = dto.CountsQuantileIndexes{
- Counts: counts2,
- QuantileIndexes: map[string]int{
- "p50": sort.SearchFloat64s(buckets, p50[1]),
- "p90": sort.SearchFloat64s(buckets, p90[1]),
- "p99": sort.SearchFloat64s(buckets, p99[1]),
- },
- }
- return nil
- }
- func (s *UrlMapping) ErrorsFromClickhouse(req dto.UrlMappingErrorsReq, resp *dto.UrlMappingErrorsResp) error {
- startTime, endTime := req.TimeBase-req.TimeValue*24*60*60, req.TimeBase
- timeField := "formatDateTime(toStartOfDay(Timestamp), '%F', 'PRC') as StartTime"
- if req.TimeType == "hour" {
- startTime = req.TimeBase - req.TimeValue*60*60
- timeField = "formatDateTime(toStartOfHour(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- }
- appAlias := ""
- if err := s.Orm.Model(&models.App{}).Where("id", req.AppId).Pluck("alias", &appAlias).Error; err != nil {
- return errors.Wrap(err, "获取应用别名失败")
- }
- list := []struct {
- StartTime string
- Total int64
- }{}
- if err := s.ChOrm.Model(&models.TracesURL{}).
- Select(fmt.Sprintf("%s, count() as Total", timeField)).
- Where("AppAlias", appAlias).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", startTime, endTime).
- Where("StatusCode>=400").Group("StartTime").Order("StartTime asc").Find(&list).Error; err != nil {
- return errors.Wrap(err, "查询错误分布数据失败")
- }
- resp.Times = make([]string, len(list))
- resp.Totals = make([]int64, len(list))
- for i, item := range list {
- resp.Times[i] = item.StartTime
- resp.Totals[i] = item.Total
- }
- return nil
- }
- // 接口错误相关统计
- func (s *UrlMapping) Errors(req dto.UrlMappingErrorsReq, resp *dto.UrlMappingErrorsResp) error {
- labels := map[string]string{
- "app_id": req.AppId,
- "error": "true",
- }
- if req.UrlType != "" {
- labels["url_type"] = req.UrlType
- }
- mins := int64(0)
- unit := "m"
- if req.TimeType == "day" {
- mins = int64(req.TimeValue) * 24 * 60
- unit = "d"
- } else if req.TimeType == "hour" {
- unit = "h"
- mins = int64(req.TimeValue) * 60
- } else {
- return errors.New("无效的time type")
- }
- name := "observe_server_duration_milliseconds"
- hist := prometheus.NewHistogram(nil, name, labels, time.Unix(int64(req.TimeBase), 0), mins)
- times := []time.Time{}
- totals := []float64{}
- err := hist.TotalUnits(unit, ×, &totals)
- if err != nil {
- return err
- }
- resp.Times = make([]string, len(times))
- resp.Totals = make([]int64, len(totals))
- for i, t := range times {
- resp.Times[i] = t.Format(time.DateOnly)
- if req.TimeType == "hour" {
- resp.Times[i] = t.Format("2006-01-02 15:00:00")
- }
- resp.Totals[i] = int64(totals[i])
- }
- return nil
- }
- func (s *UrlMapping) SlowTop(req *dto.UrlMappingSlowTopReq, resp *[]dto.UrlMappingSlowTopResp) error {
- req.CheckFilling(time.Hour)
- if req.EndTime-req.StartTime > 3600 {
- req.StartTime = req.EndTime - 3600
- }
- app := models.App{}
- if err := s.Orm.Model(&models.App{}).Where("id", req.AppId).Find(&app).Error; err != nil {
- return errors.Wrap(err, "appid非法")
- }
- if err := s.ChOrm.Model(&models.TracesURL{}).
- Select("AppAlias, ServiceName, IF(Route!='', Route, Path) AS Route, Method, SUM(IF(StatusCode>=400, 1, 0))/COUNT() ErrorRate, AVG(Duration)/1e6 Duration").
- Where("AppAlias", app.Alias).
- Where("Timestamp>=toDateTime(?) AND Timestamp<toDateTime(?) and Route!=''", req.StartTime, req.EndTime).
- Group("AppAlias, ServiceName, Route, Method").
- Order("Duration DESC").Limit(int(req.Limit)).
- Find(resp).Error; err != nil {
- return errors.Wrap(err, "获取trace url失败")
- }
- svcMap, err := query.NewService().ServiceMap(app.Alias)
- if err != nil {
- return err
- }
- wg := sync.WaitGroup{}
- wg.Add(len(*resp) * 2)
- for i := range *resp {
- item := (*resp)[i]
- // (*resp)[i].ErrorRate = (*resp)[i].ErrorRate
- (*resp)[i].Duration = math.Round(item.Duration*100) / 100
- (*resp)[i].ServiceNameCN = svcMap[item.ServiceName]
- go s.routeName(&wg, item.ServiceName, item.Method, item.Route, &((*resp)[i].Name))
- if config.ExtConfig.ClickhouseMetrics {
- go s.slowtopStatsFromClickhouse(&wg, req, &((*resp)[i]))
- } else {
- go s.slowtopStats(&wg, req, &((*resp)[i]))
- }
- }
- wg.Wait()
- return nil
- }
- func (s *UrlMapping) slowtopStatsFromClickhouse(wg *sync.WaitGroup, req *dto.UrlMappingSlowTopReq, item *dto.UrlMappingSlowTopResp) {
- defer wg.Done()
- timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- if req.EndTime-req.StartTime >= 3600 {
- timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- }
- fields := []string{
- timeField,
- "quantile(0.5)(Duration)/1e6 as P50Duration",
- "quantile(0.90)(Duration)/1e6 as P90Duration",
- "quantile(0.99)(Duration)/1e6 as P99Duration",
- }
- quantiles := []struct {
- StartTime string
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }{}
- if err := s.ChOrm.Model(&models.TracesURL{}).Select(fields).
- Where("Route", item.Route).
- Where("Method", item.Method).
- Where("ServiceName", item.ServiceName).
- Where("AppAlias", item.AppAlias).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime).
- Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
- return
- }
- item.DurationStats = dto.DurationStats{
- Time: make([]string, 0, len(quantiles)),
- P50: make([]float64, 0, len(quantiles)),
- P90: make([]float64, 0, len(quantiles)),
- P99: make([]float64, 0, len(quantiles)),
- }
- for _, quantile := range quantiles {
- item.DurationStats.Time = append(item.DurationStats.Time, quantile.StartTime)
- item.DurationStats.P50 = append(item.DurationStats.P50, math.Round(quantile.P50Duration*100)/100)
- item.DurationStats.P90 = append(item.DurationStats.P90, math.Round(quantile.P90Duration*100)/100)
- item.DurationStats.P99 = append(item.DurationStats.P99, math.Round(quantile.P99Duration*100)/100)
- }
- }
- func (s *UrlMapping) slowtopStats(wg *sync.WaitGroup, req *dto.UrlMappingSlowTopReq, item *dto.UrlMappingSlowTopResp) {
- defer wg.Done()
- metric := "observe_server_duration_milliseconds"
- ts := time.Unix(req.EndTime, 0)
- mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
- wg2 := sync.WaitGroup{}
- labels := map[string]string{
- "url_prefix": item.Route,
- "url_method": item.Method,
- "service_name": item.ServiceName,
- "app_alias": item.AppAlias,
- "url_is_perfect_match": "1",
- }
- hist := prometheus.NewHistogram(&wg2, metric, labels, ts, int64(mins))
- p50s, p90s, p99s := []float64{}, []float64{}, []float64{}
- times := []string{}
- wg2.Add(3)
- go hist.QuantileMinutes(0.5, &[]string{}, &p50s)
- go hist.QuantileMinutes(0.9, &[]string{}, &p90s)
- go hist.QuantileMinutes(0.99, ×, &p99s)
- wg2.Wait()
- item.DurationStats = dto.DurationStats{
- Time: times,
- P50: p50s,
- P90: p90s,
- P99: p99s,
- }
- }
- func (s *UrlMapping) routeName(wg *sync.WaitGroup, serviceName, method, route string, routeName *string) error {
- defer wg.Done()
- if err := s.Orm.Model(&models.UrlMapping{}).
- Where("service_name", serviceName).
- Where("method", method).
- Where("url", route).Pluck("name", routeName).Error; err != nil {
- return errors.Wrap(err, "获取url mapping名称失败")
- }
- return nil
- }
- func (s *UrlMapping) SubDigits(req *dto.UrlMappingSubDigitsReq, resp *dto.UrlMappingSubDigitsResp) error {
- req.CheckFilling(time.Minute * 5)
- um := models.UrlMapping{}
- if err := s.Orm.Where("id", req.ID).Find(&um).Error; err != nil {
- return errors.Wrap(err, "获取url mapping信息失败")
- }
- digit := struct {
- Total int64
- ErrorNum int64
- MedianDuration float64
- MaxDuration float64
- }{}
- db := s.ChOrm.Model(&models.TracesURL{}).
- Select("count() as Total, sum(if(StatusCode>=400, 1, 0)) as ErrorNum, quantile(0.5)(Duration)/1e6 as MedianDuration, max(Duration)/1e6 as MaxDuration").
- // Where("Route like ?", um.Url+"%").Where("Method", um.Method).
- // Where("ServiceName", um.ServiceName).
- Where("Timestamp>=? and Timestamp<?", req.StartTime, req.EndTime)
- if req.ID > 0 {
- db.Where("AppAlias=? and ServiceName=?", um.AppAlias, um.ServiceName)
- db.Where("(Route=? OR Path=?) and Method=?", um.Url, um.Url, um.Method)
- } else if req.AppAlias != "" {
- db.Where("AppAlias", req.AppAlias)
- }
- if err := db.Find(&digit).Error; err != nil {
- return errors.Wrap(err, "查询数字视图数据失败")
- }
- if math.IsNaN(digit.MedianDuration) {
- digit.MedianDuration = 0
- }
- resp.Total = digit.Total
- if digit.Total > 0 {
- resp.ErrorRate = float64(digit.ErrorNum) / float64(digit.Total)
- }
- mins := float64(req.EndTime-req.StartTime) / 60
- resp.QPM = math.Round(float64(resp.Total)/mins*100) / 100
- resp.MedianDuration = math.Round(digit.MedianDuration*100) / 100
- resp.MaxDuration = math.Round(digit.MaxDuration*100) / 100
- // wg := sync.WaitGroup{}
- // metric := "observe_server_duration_milliseconds"
- // labels := map[string]string{
- // "url_level": strconv.Itoa(int(um.Level)),
- // "url_method": um.Method,
- // "url_prefix": um.Url,
- // "service_name": um.ServiceName,
- // "app_alias": um.AppAlias,
- // }
- // ts := time.Unix(req.EndTime, 0)
- // mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
- // hist := prometheus.NewHistogram(&wg, metric, labels, ts, int64(mins))
- // var errRate float64
- // wg.Add(4)
- // go hist.Total(&resp.Total)
- // go hist.ErrorRate(&errRate)
- // go hist.Quantile(0.90, &resp.MaxDuration)
- // go hist.Quantile(0.5, &resp.MedianDuration)
- // wg.Wait()
- // resp.ErrorRate = fmt.Sprintf("%.2f%%", errRate*float64(100))
- // resp.QPM = math.Round(float64(resp.Total)/mins*100) / 100
- // resp.MedianDuration = math.Round(resp.MedianDuration*100) / 100
- // resp.MaxDuration = math.Round(resp.MaxDuration*100) / 100
- return nil
- }
- func (s *UrlMapping) SubList(req *dto.UrlMappingSubListReq, resp *[]dto.UrlMappingSubListResp, count *int64) error {
- req.CheckFilling(time.Hour)
- // um := models.UrlMapping{}
- // if err := s.Orm.Where("id", req.ID).Find(&um).Error; err != nil {
- // return errors.Wrap(err, "获取url mapping信息失败")
- // }
- list := []models.UrlMapping{}
- db := s.Orm.Select("id, name, url, method, service_name, app_alias, level, favor, module, summary, type").
- Where("app_alias=?", req.AppAlias).
- // Where("url like ? AND is_perfect_match=?", um.Url+"%", 1)
- Where("is_perfect_match=?", 1).
- Where("url!=?", "")
- var serviceNames []string
- if req.ServiceName != "" {
- serviceNames = strings.Split(req.ServiceName, ",")
- if len(serviceNames) > 0 {
- db.Where("service_name in ?", serviceNames)
- }
- }
- urlMappingIds := []string{}
- if req.BizHash != "" {
- err := s.Orm.Model(&models.BizEdge{}).
- Joins("inner join ot_biz_node on ot_biz_node.id=ot_biz_edge.source or ot_biz_node.id=ot_biz_edge.target").
- Where("ot_biz_edge.biz_hash=? and external_id>0", req.BizHash).
- Pluck("external_id", &urlMappingIds).Error
- if err != nil {
- return errors.Wrap(err, "获取extend id失败")
- }
- }
- if len(urlMappingIds) > 0 {
- db.Where("id in ?", urlMappingIds)
- }
- if req.Url != "" {
- db.Where("url like ?", "%"+req.Url+"%")
- }
- if err := db.
- Scopes(cDto.Paginate(req.GetPageSize(), req.GetPageIndex())).
- Find(&list).Offset(-1).Limit(-1).Count(count).Error; err != nil {
- return errors.Wrap(err, "获取接口映射子列表失败")
- }
- *resp = make([]dto.UrlMappingSubListResp, len(list))
- svcMap, err := query.NewService().ServiceMap(req.AppAlias)
- if err != nil {
- return errors.Wrap(err, "获取service映射失败")
- }
- wg := sync.WaitGroup{}
- wg.Add(len(list))
- for i := range list {
- item := list[i]
- (*resp)[i] = dto.UrlMappingSubListResp{
- ID: item.ID,
- Name: item.Name,
- Route: item.Url,
- Method: item.Method,
- ServiceName: item.ServiceName,
- ServiceNameCN: svcMap[item.ServiceName],
- AppAlias: item.AppAlias,
- Favor: int(item.Favor),
- Module: item.Module,
- Summary: item.Summary,
- }
- if config.ExtConfig.ClickhouseMetrics {
- go s.subStatsFromClickhouse(&wg, req, &(*resp)[i])
- } else {
- go s.SubStats(&wg, &item, req, &(*resp)[i])
- }
- }
- wg.Wait()
- return nil
- }
- func (s *UrlMapping) subStatsFromClickhouse(wg *sync.WaitGroup, req *dto.UrlMappingSubListReq, item *dto.UrlMappingSubListResp) {
- defer wg.Done()
- db := s.ChOrm.Model(&models.TracesURL{}).
- Where("AppAlias=? and ServiceName=? and Method=? and Route=?", item.AppAlias, item.ServiceName, item.Method, item.Route).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime)
- fields := []string{
- "count() as Total",
- "max(Duration) as MaxDuration",
- "avg(Duration) as AvgDuration",
- "sum(if(StatusCode>=400, 1, 0)) as ErrorNum",
- "quantile(0.5)(Duration) as P50Duration",
- "quantile(0.90)(Duration) as P90Duration",
- "quantile(0.99)(Duration) as P99Duration",
- }
- row := struct {
- Total int64
- MaxDuration int64
- ErrorNum int64
- AvgDuration float64
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }{}
- err := db.Select(fields).Find(&row).Error
- if err != nil {
- return
- }
- if row.Total == 0 {
- item.DurationStats = dto.DurationStats{
- Time: []string{},
- P50: []float64{},
- P90: []float64{},
- P99: []float64{},
- }
- return
- }
- timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- if req.EndTime-req.StartTime >= 60*60 {
- timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- }
- fields = []string{
- timeField,
- "quantile(0.5)(Duration) as P50Duration",
- "quantile(0.90)(Duration) as P90Duration",
- "quantile(0.99)(Duration) as P99Duration",
- }
- quantiles := []struct {
- StartTime string
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }{}
- if err := db.Select(fields).Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
- return
- }
- item.Total = row.Total
- item.DurationStats = dto.DurationStats{
- Time: make([]string, len(quantiles)),
- P50: make([]float64, len(quantiles)),
- P90: make([]float64, len(quantiles)),
- P99: make([]float64, len(quantiles)),
- }
- for i, quantile := range quantiles {
- item.DurationStats.Time[i] = quantile.StartTime
- item.DurationStats.P50[i] = math.Round(quantile.P50Duration/1e6*100) / 100
- item.DurationStats.P90[i] = math.Round(quantile.P90Duration/1e6*100) / 100
- item.DurationStats.P99[i] = math.Round(quantile.P99Duration/1e6*100) / 100
- }
- }
- func (s *UrlMapping) SubStats(wg *sync.WaitGroup, um *models.UrlMapping, req *dto.UrlMappingSubListReq, resp *dto.UrlMappingSubListResp) error {
- defer wg.Done()
- wg2 := sync.WaitGroup{}
- metric := "observe_server_duration_milliseconds"
- labels := map[string]string{
- "url_level": strconv.Itoa(int(um.Level)),
- "url_is_perfect_match": "1",
- "url_method": um.Method,
- "url_prefix": um.Url,
- "service_name": um.ServiceName,
- "app_alias": um.AppAlias,
- }
- ts := time.Unix(req.EndTime, 0)
- mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
- hist := prometheus.NewHistogram(&wg2, metric, labels, ts, int64(mins))
- wg2.Add(5)
- var errorRate float64
- go hist.Total(&resp.Total)
- go hist.ErrorRate(&errorRate)
- times, p50s, p90s, p99s := []string{}, []float64{}, []float64{}, []float64{}
- go hist.QuantileMinutes(0.5, &[]string{}, &p50s)
- go hist.QuantileMinutes(0.9, &[]string{}, &p90s)
- go hist.QuantileMinutes(0.99, ×, &p99s)
- wg2.Wait()
- resp.ErrorRate = errorRate
- resp.DurationStats = dto.DurationStats{
- Time: times,
- P50: p50s,
- P90: p90s,
- P99: p99s,
- }
- return nil
- }
- func (s *UrlMapping) ChartQPS(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
- wg := sync.WaitGroup{}
- hist, timeUnit, timeList := s.chartCommon(req, &wg)
- group := ""
- if req.Type == "status_trend" {
- group = "status_code"
- }
- times, values := map[string][]string{}, map[string][]float64{}
- wg.Add(1)
- go hist.QPS(timeUnit, group, ×, &values)
- wg.Wait()
- resp.Times = timeList
- if group == "status_code" {
- resp.Values = map[string][]float64{
- "2XX": make([]float64, len(timeList)),
- "3XX": make([]float64, len(timeList)),
- "4XX": make([]float64, len(timeList)),
- "5XX": make([]float64, len(timeList)),
- }
- for key := range times {
- if key < "200" {
- continue
- }
- k := fmt.Sprintf("%cXX", key[0])
- for i, j := 0, 0; j < len(times[key]) && i < len(timeList); i++ {
- if timeList[i] == times[key][j] {
- resp.Values[k][i] += math.Round(values[key][j]*60*100) / 100 // *60是为了将QPS转为数量
- j++
- }
- // else 此时 times[key]中缺少某一时间
- }
- }
- } else {
- key := ""
- resp.Values = map[string][]float64{
- key: make([]float64, len(timeList)),
- }
- for i, j := 0, 0; j < len(times[key]) && i < len(timeList); i++ {
- if timeList[i] == times[key][j] {
- resp.Values[key][i] += values[key][j]
- j++
- }
- // else 此时 times[key]中缺少某一时间
- }
- }
- return nil
- }
- func (s *UrlMapping) ChartQPSFromClickhouse(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
- req.CheckFilling(time.Minute * 5)
- timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- if req.EndTime-req.StartTime >= 60*60 {
- timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- }
- db := s.ChOrm.Model(&models.TracesURL{}).
- Where("AppAlias", req.AppAlias).
- Where("ServiceName", req.ServiceName).
- Where("Method", req.Method).
- Where("Route=? or Path=?", req.Route, req.Route).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime)
- if req.Type == "status_trend" {
- list := []struct {
- StartTime string
- StatusCodes string
- Total int64
- }{}
- fields := fmt.Sprintf("%s,if(StatusCode>=500, '5XX', if(StatusCode>=400, '4XX', if(StatusCode>=300, '3XX', '2XX'))) StatusCodes, count() Total", timeField)
- if err := db.Select(fields).
- Group("StartTime, StatusCodes").Order("StartTime asc, StatusCodes asc").Find(&list).Error; err != nil {
- return errors.Wrap(err, "获取QPS相关数据失败")
- }
- resp.Times = []string{}
- resp.Values = map[string][]float64{
- "2XX": {},
- "3XX": {},
- "4XX": {},
- "5XX": {},
- }
- for _, item := range list {
- if len(resp.Times) > 0 {
- if item.StartTime != resp.Times[len(resp.Times)-1] {
- resp.Times = append(resp.Times, item.StartTime)
- }
- } else {
- resp.Times = append(resp.Times, item.StartTime)
- }
- resp.Values[item.StatusCodes] = append(resp.Values[item.StatusCodes], float64(item.Total))
- }
- } else {
- list := []struct {
- StartTime string
- Total int64
- }{}
- if err := db.Select(fmt.Sprintf("%s, count() Total", timeField)).
- Group("StartTime").Order("StartTime asc").Find(&list).Error; err != nil {
- return errors.Wrap(err, "获取QPS相关数据失败")
- }
- resp.Times = []string{}
- resp.Values = map[string][]float64{
- "": {},
- }
- for _, item := range list {
- resp.Times = append(resp.Times, item.StartTime)
- resp.Values[""] = append(resp.Values[""], float64(item.Total))
- }
- }
- return nil
- }
- func (s *UrlMapping) ChartLatencyFromClickhouse(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
- req.CheckFilling(time.Minute * 5)
- timeField := "formatDateTime(toStartOfMinute(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- if req.EndTime-req.StartTime >= 60*60 {
- timeField = "formatDateTime(toStartOfFiveMinutes(Timestamp), '%F %H:%i', 'PRC') as StartTime"
- }
- fields := []string{
- timeField,
- "quantile(0.5)(Duration)/1e6 as P50Duration",
- "quantile(0.90)(Duration)/1e6 as P90Duration",
- "quantile(0.99)(Duration)/1e6 as P99Duration",
- }
- quantiles := []struct {
- StartTime string
- P50Duration float64
- P90Duration float64
- P99Duration float64
- }{}
- if err := s.ChOrm.Model(&models.TracesURL{}).Select(fields).
- Where("AppAlias", req.AppAlias).
- Where("ServiceName", req.ServiceName).
- Where("Method", req.Method).
- Where("Route=? or Path=?", req.Route, req.Route).
- Where("Timestamp>toDateTime(?) and Timestamp<toDateTime(?)", req.StartTime, req.EndTime).
- Group(timeField).Order(fmt.Sprintf("%s asc", timeField)).Find(&quantiles).Error; err != nil {
- return errors.Wrap(err, "获取分位数数据失败")
- }
- resp.Times = []string{}
- resp.Values = map[string][]float64{
- "P50": {},
- "P90": {},
- "P99": {},
- }
- for _, quantile := range quantiles {
- resp.Times = append(resp.Times, quantile.StartTime)
- resp.Values["P50"] = append(resp.Values["P50"], math.Round(quantile.P50Duration*100)/100)
- resp.Values["P90"] = append(resp.Values["P90"], math.Round(quantile.P50Duration*100)/100)
- resp.Values["P99"] = append(resp.Values["P99"], math.Round(quantile.P50Duration*100)/100)
- }
- return nil
- }
- func (s *UrlMapping) ChartLatency(req *dto.UrlMappingChartReq, resp *dto.UrlMappingChartResp) error {
- wg := sync.WaitGroup{}
- hist, timeUnit, timeList := s.chartCommon(req, &wg)
- times50, times90, times99 := []string{}, []string{}, []string{}
- values50, values90, values99 := []float64{}, []float64{}, []float64{}
- wg.Add(3)
- go hist.QuantileTimeUnit(timeUnit, 0.5, ×50, &values50)
- go hist.QuantileTimeUnit(timeUnit, 0.9, ×90, &values90)
- go hist.QuantileTimeUnit(timeUnit, 0.99, ×99, &values99)
- wg.Wait()
- resp.Times = timeList
- times := map[string][]string{
- "P50": times50,
- "P90": times90,
- "P99": times99,
- }
- values := map[string][]float64{
- "P50": values50,
- "P90": values90,
- "P99": values99,
- }
- resp.Values = map[string][]float64{
- "P50": make([]float64, len(timeList)),
- "P90": make([]float64, len(timeList)),
- "P99": make([]float64, len(timeList)),
- }
- for key := range times {
- for i, j := 0, 0; j < len(times[key]) && i < len(timeList); i++ {
- if timeList[i] == times[key][j] {
- resp.Values[key][i] += values[key][j]
- j++
- }
- // else 此时 times[key]中缺少某一时间
- }
- }
- return nil
- }
- func (s *UrlMapping) chartCommon(req *dto.UrlMappingChartReq, wg *sync.WaitGroup) (prometheus.Histogram, string, []string) {
- metric := "observe_server_duration_milliseconds"
- labels := map[string]string{
- "url_is_perfect_match": "1",
- "app_alias": req.AppAlias,
- }
- if req.ServiceName != "" {
- labels["service_name"] = req.ServiceName
- }
- if req.Method != "" {
- labels["url_method"] = req.Method
- }
- if req.Route != "" {
- labels["url_prefix"] = req.Route
- }
- req.CheckFilling(time.Minute * 5)
- ts := time.Unix(req.EndTime, 0)
- mins := time.Unix(req.EndTime, 0).Sub(time.Unix(req.StartTime, 0)).Minutes()
- fmt.Println("#######-----", mins, req.EndTime, req.StartTime)
- timeUnit := "d"
- layout := time.DateOnly
- diff := 60 * 60 * 24
- startTime := req.StartTime - req.StartTime%int64(diff)
- endTime := req.EndTime - req.EndTime%int64(diff)
- if mins <= 60 {
- timeUnit = "m"
- layout = "2006-01-02 15:04"
- diff = 60
- startTime = req.StartTime - req.StartTime%int64(diff)
- endTime = req.EndTime - req.EndTime%int64(diff)
- ts = time.Unix(endTime, 0)
- } else if mins <= 24*60 {
- timeUnit = "h"
- diff = 60 * 60
- layout = "2006-01-02 15"
- startTime = req.StartTime - req.StartTime%int64(diff)
- endTime = req.EndTime - req.EndTime%int64(diff)
- ts = time.Unix(endTime, 0)
- } else {
- // 兼容 prometheus 特殊情况
- // 当 时间哦 2024-5-27 9:48:45 ~ 2024-5-27 10:48:45, prometheus计算返回的时间是 2024-5-27 9:48到2024-5-27 10:48
- // 当 时间哦 2024-5-27 8:48:45 ~ 2024-5-27 10:48:45, prometheus计算返回的时间是 2024-5-27 8到2024-5-27 10
- // 而当 时间哦 2024-5-21 10:48:45 ~ 2024-5-27 10:48:45, prometheus计算返回的时间确是 2024-5-22 到2024-5-27, 少了一天,所以此处+1440分钟, 多加一天, 让返回结果为 2024-5-21 到2024-5-27
- mins += 1440
- }
- times := []string{}
- for startTime <= endTime {
- times = append(times, time.Unix(startTime, 0).Format(layout))
- startTime += int64(diff)
- }
- return prometheus.NewHistogram(wg, metric, labels, ts, int64(mins)), timeUnit, times
- }
- func (s *UrlMapping) BaseInfo(req *dto.UrlMappingBaseInfoReq, resp *dto.UrlMappingBaseInfoResp) error {
- u, err := url.Parse(req.Url)
- if err != nil {
- return errors.Wrap(err, "非法url")
- }
- db := s.Orm.Model(&models.UrlMapping{}).Where("url", u.Path).Where("is_perfect_match", 1)
- if req.AppAlias != "" {
- db.Where("app_alias", req.AppAlias)
- }
- if req.ServiceName != "" {
- db.Where("service_name", req.ServiceName)
- }
- if req.Method != "" {
- db.Where("method", req.Method)
- }
- um := models.UrlMapping{}
- if err := db.First(&um).Error; err != nil {
- return errors.Wrap(err, "获取url基础信息失败")
- }
- var appName string
- if err := s.Orm.Model(&models.App{}).Where("alias", um.AppAlias).Pluck("name", &appName).Error; err != nil {
- return errors.Wrap(err, "获取应用名称失败")
- }
- var serviceNameCn string
- if err := s.Orm.Model(&models.ServiceNode{}).
- Where("service_name", um.ServiceName).Where("app_alias", um.AppAlias).Pluck("name", &serviceNameCn).Error; err != nil {
- return errors.Wrap(err, "获取应用名称失败")
- }
- // bizNodeId := int64(0)
- // if err := s.Orm.Model(&models.BizNode{}).
- // Where("app_alias", um.AppAlias).Where("service_name", um.ServiceName).Where("span_kind='SPAN_KIND_SERVER'").
- // Where("span_name like ?", "%"+um.Url).Pluck("id", &bizNodeId).Error; err != nil {
- // return errors.Wrap(err, "获取业务结点id失败")
- // }
- // bizId := int64(0)
- // if err := s.Orm.Model(&models.BizEdge{}).Where("source=? or target=?", bizNodeId, bizNodeId).Pluck("biz_id", &bizId).Error; err != nil {
- // return errors.Wrap(err, "获取业务id失败")
- // }
- resp.ID = um.ID
- resp.AppAlias = um.AppAlias
- resp.AppName = appName
- resp.Method = um.Method
- resp.Route = um.Url
- resp.ServiceName = um.ServiceName
- resp.ServiceNameCn = serviceNameCn
- // resp.BizID = bizId
- // resp.BizNodeID = bizNodeId
- return nil
- }
- func (s UrlMapping) Match(req *dto.UrlMappingMatchReq, resp *[]dto.UrlMappingMatchResp, total *int64) error {
- u, err := url.Parse(req.Url)
- if err != nil {
- return errors.Wrap(err, "非法url")
- }
- db := s.Orm.Model(&models.UrlMapping{}).Where("url like ?", fmt.Sprintf("%%%s%%", u.Path)).Where("is_perfect_match", 1)
- if req.AppAlias != "" {
- db.Where("app_alias", req.AppAlias)
- }
- if req.ServiceName != "" {
- db.Where("service_name", req.ServiceName)
- }
- if req.Method != "" {
- db.Where("method", req.Method)
- }
- ums := []models.UrlMapping{}
- if err := db.Scopes(cDto.Paginate(req.GetPageSize(), req.GetPageIndex())).
- Order("level asc").Find(&ums).Limit(-1).Offset(-1).Count(total).Error; err != nil {
- return errors.Wrap(err, "获取url信息失败")
- }
- for _, um := range ums {
- *resp = append(*resp, dto.UrlMappingMatchResp{
- AppAlias: um.AppAlias,
- ServiceName: um.ServiceName,
- Route: um.Url,
- Method: um.Method,
- })
- }
- return nil
- }
|