Files
oneterm/backend/api/controller/stat.go
2024-08-20 17:46:22 +08:00

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
}