mirror of
				https://github.com/veops/oneterm.git
				synced 2025-10-31 02:46:29 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package controller
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"sort"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/gin-gonic/gin"
 | |
| 	"github.com/samber/lo"
 | |
| 	"golang.org/x/sync/errgroup"
 | |
| 
 | |
| 	"github.com/veops/oneterm/acl"
 | |
| 	redis "github.com/veops/oneterm/cache"
 | |
| 	mysql "github.com/veops/oneterm/db"
 | |
| 	"github.com/veops/oneterm/model"
 | |
| )
 | |
| 
 | |
| // StatAssetType godoc
 | |
| //
 | |
| //	@Tags		stat
 | |
| //	@Success	200	{object}	HttpResponse{data=ListData{list=[]model.StatAssetType}}
 | |
| //	@Router		/stat/assettype [get]
 | |
| func (c *Controller) StatAssetType(ctx *gin.Context) {
 | |
| 	stat := make([]*model.StatAssetType, 0)
 | |
| 	key := "stat-assettype"
 | |
| 	if redis.Get(ctx, key, stat) == nil {
 | |
| 		ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	m, err := nodeCountAsset()
 | |
| 	if err != nil {
 | |
| 		ctx.AbortWithError(http.StatusInternalServerError, err)
 | |
| 		return
 | |
| 	}
 | |
| 	if err = mysql.DB.
 | |
| 		Model(stat).
 | |
| 		Where("parent_id = 0").
 | |
| 		Find(&stat).
 | |
| 		Error; err != nil {
 | |
| 		ctx.AbortWithError(http.StatusInternalServerError, err)
 | |
| 		return
 | |
| 	}
 | |
| 	for _, s := range stat {
 | |
| 		s.Count = m[s.Id]
 | |
| 	}
 | |
| 
 | |
| 	redis.SetEx(ctx, key, stat, time.Minute)
 | |
| 
 | |
| 	ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| }
 | |
| 
 | |
| // StatCount godoc
 | |
| //
 | |
| //	@Tags		stat
 | |
| //	@Success	200	{object}	HttpResponse{data=model.StatCount}
 | |
| //	@Router		/stat/count [get]
 | |
| func (c *Controller) StatCount(ctx *gin.Context) {
 | |
| 	stat := &model.StatCount{}
 | |
| 	key := "stat-count"
 | |
| 	if redis.Get(ctx, key, stat) == nil {
 | |
| 		ctx.JSON(http.StatusOK, NewHttpResponseWithData(stat))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	eg := &errgroup.Group{}
 | |
| 	eg.Go(func() error {
 | |
| 		return mysql.DB.
 | |
| 			Model(&model.Session{}).
 | |
| 			Select("COUNT(DISTINCT asset_id, account_id) as connect, COUNT(DISTINCT uid) as user, COUNT(DISTINCT gateway_id) as gateway, COUNT(*) as session").
 | |
| 			Where("status = 1").
 | |
| 			First(&stat).
 | |
| 			Error
 | |
| 	})
 | |
| 	eg.Go(func() error {
 | |
| 		return mysql.DB.Model(&model.Asset{}).Count(&stat.TotalAsset).Error
 | |
| 	})
 | |
| 	eg.Go(func() error {
 | |
| 		return mysql.DB.Model(&model.Asset{}).Where("connectable = 1").Count(&stat.Asset).Error
 | |
| 	})
 | |
| 	eg.Go(func() error {
 | |
| 		return mysql.DB.Model(&model.Gateway{}).Count(&stat.TotalGateway).Error
 | |
| 	})
 | |
| 
 | |
| 	if err := eg.Wait(); err != nil {
 | |
| 		ctx.AbortWithError(http.StatusInternalServerError, err)
 | |
| 		return
 | |
| 	}
 | |
| 	stat.Gateway = lo.Ternary(stat.Gateway <= stat.TotalGateway, stat.Gateway, stat.TotalGateway)
 | |
| 
 | |
| 	redis.SetEx(ctx, key, stat, time.Minute)
 | |
| 
 | |
| 	ctx.JSON(http.StatusOK, NewHttpResponseWithData(stat))
 | |
| }
 | |
| 
 | |
| // StatAccount godoc
 | |
| //
 | |
| //	@Tags		stat
 | |
| //	@Param		type		query		string	true	"account name" Enums(day, week, month)
 | |
| //	@Success	200	{object}	HttpResponse{data=ListData{list=[]model.StatAccount}}
 | |
| //	@Router		/stat/account [get]
 | |
| func (c *Controller) StatAccount(ctx *gin.Context) {
 | |
| 	start, end := time.Now(), time.Now()
 | |
| 	switch ctx.Query("type") {
 | |
| 	case "day":
 | |
| 		start = start.Add(-time.Hour * 24)
 | |
| 	case "week":
 | |
| 		start = start.Add(-time.Hour * 24 * 7)
 | |
| 	case "month":
 | |
| 		start = start.Add(-time.Hour * 24 * 30)
 | |
| 	default:
 | |
| 		ctx.AbortWithError(http.StatusBadRequest, fmt.Errorf("wrong time range %s", ctx.Query("type")))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	stat := make([]*model.StatAccount, 0)
 | |
| 	key := "stat-account-" + ctx.Query("type")
 | |
| 	if redis.Get(ctx, key, stat) == nil {
 | |
| 		ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	err := mysql.DB.
 | |
| 		Model(&model.Account{}).
 | |
| 		Select("account.name, COUNT(*) AS count").
 | |
| 		Joins("LEFT JOIN session ON account.id = session.account_id").
 | |
| 		Group("account.id").
 | |
| 		Order("count DESC").
 | |
| 		Limit(10).
 | |
| 		Where("session.created_at >= ? AND session.created_at <= ?", start, end).
 | |
| 		Find(&stat).
 | |
| 		Error
 | |
| 	if err != nil {
 | |
| 		ctx.AbortWithError(http.StatusInternalServerError, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	redis.SetEx(ctx, key, stat, time.Minute)
 | |
| 
 | |
| 	ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| }
 | |
| 
 | |
| // StatAsset godoc
 | |
| //
 | |
| //	@Tags		stat
 | |
| //	@Param		type		query		string	true	"account name" Enums(day, week, month)
 | |
| //	@Success	200	{object}	HttpResponse{data=ListData{list=[]model.StatAsset}}
 | |
| //	@Router		/stat/asset [get]
 | |
| func (c *Controller) StatAsset(ctx *gin.Context) {
 | |
| 	start, end := time.Now(), time.Now()
 | |
| 	interval := time.Hour * 24
 | |
| 	dateFmt := "%Y-%m-%d"
 | |
| 	timeFmt := time.DateOnly
 | |
| 	switch ctx.Query("type") {
 | |
| 	case "day":
 | |
| 		start = start.Add(-time.Hour * 24)
 | |
| 		interval = time.Hour
 | |
| 		dateFmt = "%Y-%m-%d %H:00:00"
 | |
| 		timeFmt = time.DateTime
 | |
| 	case "week":
 | |
| 		start = start.Add(-time.Hour * 24 * 7)
 | |
| 	case "month":
 | |
| 		start = start.Add(-time.Hour * 24 * 30)
 | |
| 	default:
 | |
| 		ctx.AbortWithError(http.StatusBadRequest, fmt.Errorf("wrong time range %s", ctx.Query("type")))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	stat := make([]*model.StatAsset, 0)
 | |
| 	key := "stat-asset-" + ctx.Query("type")
 | |
| 	if redis.Get(ctx, key, stat) == nil {
 | |
| 		ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| 		return
 | |
| 	}
 | |
| 	err := mysql.DB.
 | |
| 		Model(&model.Session{}).
 | |
| 		Select("COUNT(DISTINCT asset_id, uid) AS connect, COUNT(*) AS session, COUNT(DISTINCT asset_id) AS asset, COUNT(DISTINCT uid) AS user, DATE_FORMAT(created_at, ?) AS time", dateFmt).
 | |
| 		Where("session.created_at >= ? AND session.created_at <= ?", start, end).
 | |
| 		Group("time").
 | |
| 		Find(&stat).
 | |
| 		Error
 | |
| 	if err != nil {
 | |
| 		ctx.AbortWithError(http.StatusInternalServerError, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	for ; !start.After(end); start = start.Add(interval) {
 | |
| 		t := start.Truncate(interval).Format(timeFmt)
 | |
| 		if lo.ContainsBy(stat, func(s *model.StatAsset) bool { return t == s.Time }) {
 | |
| 			continue
 | |
| 		}
 | |
| 		stat = append(stat, &model.StatAsset{Time: t})
 | |
| 	}
 | |
| 
 | |
| 	sort.Slice(stat, func(i, j int) bool { return stat[i].Time < stat[j].Time })
 | |
| 
 | |
| 	redis.SetEx(ctx, key, stat, time.Minute)
 | |
| 
 | |
| 	ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| }
 | |
| 
 | |
| // StatCountOfUser godoc
 | |
| //
 | |
| //	@Tags		stat
 | |
| //	@Success	200	{object}	HttpResponse{data=model.StatCountOfUser}
 | |
| //	@Router		/stat/count/ofuser [get]
 | |
| func (c *Controller) StatCountOfUser(ctx *gin.Context) {
 | |
| 	currentUser, _ := acl.GetSessionFromCtx(ctx)
 | |
| 	stat := &model.StatCountOfUser{}
 | |
| 	key := fmt.Sprintf("stat-count-%d-", currentUser.Uid)
 | |
| 	if redis.Get(ctx, key, stat) == nil {
 | |
| 		ctx.JSON(http.StatusOK, NewHttpResponseWithData(stat))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	eg := &errgroup.Group{}
 | |
| 	eg.Go(func() error {
 | |
| 		return mysql.DB.
 | |
| 			Model(&model.Session{}).
 | |
| 			Select("COUNT(DISTINCT asset_id, account_id) as connect, COUNT(DISTINCT asset_id) as asset, COUNT(*) as session").
 | |
| 			Where("status = 1").
 | |
| 			Where("uid = ?", currentUser.Uid).
 | |
| 			First(&stat).
 | |
| 			Error
 | |
| 	})
 | |
| 	eg.Go(func() error {
 | |
| 		isAdmin := acl.IsAdmin(currentUser)
 | |
| 		db := mysql.DB.Model(&model.Asset{})
 | |
| 		if !isAdmin {
 | |
| 			authorizationResourceIds, err := GetAutorizationResourceIds(ctx)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			db = db.Where("id IN (?)", mysql.DB.Model(&model.Authorization{}).Select("asset_id").Where("resource_id IN ?", authorizationResourceIds))
 | |
| 		}
 | |
| 		return db.Count(&stat.TotalAsset).Error
 | |
| 	})
 | |
| 
 | |
| 	if err := eg.Wait(); err != nil {
 | |
| 		ctx.AbortWithError(http.StatusInternalServerError, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	redis.SetEx(ctx, key, stat, time.Minute)
 | |
| 
 | |
| 	ctx.JSON(http.StatusOK, NewHttpResponseWithData(stat))
 | |
| }
 | |
| 
 | |
| // StatRankOfUser godoc
 | |
| //
 | |
| //	@Tags		stat
 | |
| //	@Success	200	{object}	HttpResponse{data=ListData{list=[]model.StatAsset}}
 | |
| //	@Router		/stat/rank/ofuser [get]
 | |
| func (c *Controller) StatRankOfUser(ctx *gin.Context) {
 | |
| 	stat := make([]*model.StatRankOfUser, 0)
 | |
| 	key := "stat-rank-user"
 | |
| 	if redis.Get(ctx, key, stat) == nil {
 | |
| 		ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	if err := mysql.DB.
 | |
| 		Model(&model.Session{}).
 | |
| 		Select("uid, COUNT(*) AS count, MAX(created_at) AS last_time").
 | |
| 		Group("uid").
 | |
| 		Order("count DESC").
 | |
| 		Limit(3).
 | |
| 		Find(&stat).
 | |
| 		Error; err != nil {
 | |
| 		ctx.AbortWithError(http.StatusInternalServerError, err)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	redis.SetEx(ctx, key, stat, time.Minute)
 | |
| 
 | |
| 	ctx.JSON(http.StatusOK, NewHttpResponseWithData(toListData(stat)))
 | |
| }
 | |
| 
 | |
| func toListData[T any](data []T) *ListData {
 | |
| 	return &ListData{
 | |
| 		Count: int64(len(data)),
 | |
| 		List:  lo.Map(data, func(d T, _ int) any { return d }),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func nodeCountAsset() (res map[int]int64, err error) {
 | |
| 	assets := make([]*model.AssetIdPid, 0)
 | |
| 	if err = mysql.DB.Model(&model.Asset{}).Find(&assets).Error; err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 	res = make(map[int]int64)
 | |
| 	for _, a := range assets {
 | |
| 		res[a.ParentId] += 1
 | |
| 	}
 | |
| 	g := make(map[int][]int)
 | |
| 	for _, n := range assets {
 | |
| 		g[n.ParentId] = append(g[n.ParentId], n.Id)
 | |
| 	}
 | |
| 	var dfs func(int) int64
 | |
| 	dfs = func(x int) int64 {
 | |
| 		for _, y := range g[x] {
 | |
| 			res[x] += dfs(y)
 | |
| 		}
 | |
| 		return res[x]
 | |
| 	}
 | |
| 	dfs(0)
 | |
| 
 | |
| 	return
 | |
| }
 | 
