|
|
@@ -0,0 +1,386 @@
|
|
|
+package model
|
|
|
+
|
|
|
+import (
|
|
|
+ "one-api/common"
|
|
|
+ "strconv"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// ChannelStatistics 按渠道统计的数据结构
|
|
|
+type ChannelStatistics struct {
|
|
|
+ Time string `json:"time"`
|
|
|
+ ChannelId int `json:"channel_id"`
|
|
|
+ ChannelName string `json:"channel_name"`
|
|
|
+ Quota int `json:"quota"`
|
|
|
+ Count int `json:"count"`
|
|
|
+}
|
|
|
+
|
|
|
+// TokenStatistics 按令牌统计的数据结构
|
|
|
+type TokenStatistics struct {
|
|
|
+ Time string `json:"time"`
|
|
|
+ TokenName string `json:"token_name"`
|
|
|
+ Quota int `json:"quota"`
|
|
|
+ Count int `json:"count"`
|
|
|
+}
|
|
|
+
|
|
|
+// UserStatistics 按用户统计的数据结构
|
|
|
+type UserStatistics struct {
|
|
|
+ Time string `json:"time"`
|
|
|
+ Username string `json:"username"`
|
|
|
+ Quota int `json:"quota"`
|
|
|
+ Count int `json:"count"`
|
|
|
+}
|
|
|
+
|
|
|
+// GetChannelStatistics 获取按渠道统计的数据
|
|
|
+func GetChannelStatistics(startTimestamp, endTimestamp int, username, tokenName, modelName string, channel int, group, defaultTime string) ([]ChannelStatistics, error) {
|
|
|
+ var statistics []ChannelStatistics
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ tx := LOG_DB.Table("logs").Select(`
|
|
|
+ created_at,
|
|
|
+ channel_id,
|
|
|
+ COUNT(*) as count,
|
|
|
+ SUM(quota) as quota
|
|
|
+ `).Where("type = ?", 2) // LogTypeConsume = 2
|
|
|
+
|
|
|
+ if username != "" {
|
|
|
+ tx = tx.Where("username = ?", username)
|
|
|
+ }
|
|
|
+ if tokenName != "" {
|
|
|
+ tx = tx.Where("token_name = ?", tokenName)
|
|
|
+ }
|
|
|
+ if modelName != "" {
|
|
|
+ tx = tx.Where("model_name LIKE ?", "%"+modelName+"%")
|
|
|
+ }
|
|
|
+ if channel != 0 {
|
|
|
+ tx = tx.Where("channel_id = ?", channel)
|
|
|
+ }
|
|
|
+ if group != "" {
|
|
|
+ tx = tx.Where("`group` = ?", group)
|
|
|
+ }
|
|
|
+ if startTimestamp != 0 {
|
|
|
+ tx = tx.Where("created_at >= ?", startTimestamp)
|
|
|
+ }
|
|
|
+ if endTimestamp != 0 {
|
|
|
+ tx = tx.Where("created_at <= ?", endTimestamp)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按时间粒度分组 - 使用数据库无关的函数
|
|
|
+ var groupBy string
|
|
|
+ switch defaultTime {
|
|
|
+ case "hour":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "channel_id, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d %H:00:00')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "channel_id, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD HH24:00:00')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "channel_id, strftime('%Y-%m-%d %H:00:00', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ case "day":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "channel_id, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "channel_id, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "channel_id, strftime('%Y-%m-%d', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ case "week":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "channel_id, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%U')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "channel_id, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-IW')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "channel_id, strftime('%Y-%W', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "channel_id, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "channel_id, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "channel_id, strftime('%Y-%m-%d', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按渠道和时间分组
|
|
|
+ tx = tx.Group(groupBy).Order("MIN(created_at) ASC")
|
|
|
+
|
|
|
+ // 执行查询
|
|
|
+ var results []struct {
|
|
|
+ CreatedAt int64 `json:"created_at"`
|
|
|
+ ChannelId int `json:"channel_id"`
|
|
|
+ Count int `json:"count"`
|
|
|
+ Quota int `json:"quota"`
|
|
|
+ }
|
|
|
+
|
|
|
+ err := tx.Scan(&results).Error
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取渠道名称映射
|
|
|
+ channelIds := make([]int, 0)
|
|
|
+ channelMap := make(map[int]string)
|
|
|
+ for _, result := range results {
|
|
|
+ if result.ChannelId != 0 {
|
|
|
+ channelIds = append(channelIds, result.ChannelId)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(channelIds) > 0 {
|
|
|
+ var channels []struct {
|
|
|
+ Id int `gorm:"column:id"`
|
|
|
+ Name string `gorm:"column:name"`
|
|
|
+ }
|
|
|
+ if err = DB.Table("channels").Select("id, name").Where("id IN ?", channelIds).Find(&channels).Error; err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ for _, channel := range channels {
|
|
|
+ channelMap[channel.Id] = channel.Name
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化时间并构建返回结果
|
|
|
+ for _, result := range results {
|
|
|
+ timeStr := formatTime(result.CreatedAt, defaultTime)
|
|
|
+ statistics = append(statistics, ChannelStatistics{
|
|
|
+ Time: timeStr,
|
|
|
+ ChannelId: result.ChannelId,
|
|
|
+ ChannelName: channelMap[result.ChannelId],
|
|
|
+ Quota: result.Quota,
|
|
|
+ Count: result.Count,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return statistics, nil
|
|
|
+}
|
|
|
+
|
|
|
+// GetTokenStatistics 获取按令牌统计的数据
|
|
|
+func GetTokenStatistics(startTimestamp, endTimestamp int, username, tokenName, modelName string, channel int, group, defaultTime string) ([]TokenStatistics, error) {
|
|
|
+ var statistics []TokenStatistics
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ tx := LOG_DB.Table("logs").Select(`
|
|
|
+ created_at,
|
|
|
+ token_name,
|
|
|
+ COUNT(*) as count,
|
|
|
+ SUM(quota) as quota
|
|
|
+ `).Where("type = ?", 2) // LogTypeConsume = 2
|
|
|
+
|
|
|
+ if username != "" {
|
|
|
+ tx = tx.Where("username = ?", username)
|
|
|
+ }
|
|
|
+ if tokenName != "" {
|
|
|
+ tx = tx.Where("token_name = ?", tokenName)
|
|
|
+ }
|
|
|
+ if modelName != "" {
|
|
|
+ tx = tx.Where("model_name LIKE ?", "%"+modelName+"%")
|
|
|
+ }
|
|
|
+ if channel != 0 {
|
|
|
+ tx = tx.Where("channel_id = ?", channel)
|
|
|
+ }
|
|
|
+ if group != "" {
|
|
|
+ tx = tx.Where("`group` = ?", group)
|
|
|
+ }
|
|
|
+ if startTimestamp != 0 {
|
|
|
+ tx = tx.Where("created_at >= ?", startTimestamp)
|
|
|
+ }
|
|
|
+ if endTimestamp != 0 {
|
|
|
+ tx = tx.Where("created_at <= ?", endTimestamp)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按时间粒度分组 - 使用数据库无关的函数
|
|
|
+ var groupBy string
|
|
|
+ switch defaultTime {
|
|
|
+ case "hour":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "token_name, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d %H:00:00')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "token_name, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD HH24:00:00')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "token_name, strftime('%Y-%m-%d %H:00:00', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ case "day":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "token_name, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "token_name, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "token_name, strftime('%Y-%m-%d', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ case "week":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "token_name, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%U')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "token_name, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-IW')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "token_name, strftime('%Y-%W', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "token_name, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "token_name, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "token_name, strftime('%Y-%m-%d', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按令牌和时间分组
|
|
|
+ tx = tx.Group(groupBy).Order("MIN(created_at) ASC")
|
|
|
+
|
|
|
+ // 执行查询
|
|
|
+ var results []struct {
|
|
|
+ CreatedAt int64 `json:"created_at"`
|
|
|
+ TokenName string `json:"token_name"`
|
|
|
+ Count int `json:"count"`
|
|
|
+ Quota int `json:"quota"`
|
|
|
+ }
|
|
|
+
|
|
|
+ err := tx.Scan(&results).Error
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化时间并构建返回结果
|
|
|
+ for _, result := range results {
|
|
|
+ timeStr := formatTime(result.CreatedAt, defaultTime)
|
|
|
+ statistics = append(statistics, TokenStatistics{
|
|
|
+ Time: timeStr,
|
|
|
+ TokenName: result.TokenName,
|
|
|
+ Quota: result.Quota,
|
|
|
+ Count: result.Count,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return statistics, nil
|
|
|
+}
|
|
|
+
|
|
|
+// GetUserStatistics 获取按用户统计的数据
|
|
|
+func GetUserStatistics(startTimestamp, endTimestamp int, username, tokenName, modelName string, channel int, group, defaultTime string) ([]UserStatistics, error) {
|
|
|
+ var statistics []UserStatistics
|
|
|
+
|
|
|
+ // 构建查询条件
|
|
|
+ tx := LOG_DB.Table("logs").Select(`
|
|
|
+ created_at,
|
|
|
+ username,
|
|
|
+ COUNT(*) as count,
|
|
|
+ SUM(quota) as quota
|
|
|
+ `).Where("type = ?", 2) // LogTypeConsume = 2
|
|
|
+
|
|
|
+ if username != "" {
|
|
|
+ tx = tx.Where("username = ?", username)
|
|
|
+ }
|
|
|
+ if tokenName != "" {
|
|
|
+ tx = tx.Where("token_name = ?", tokenName)
|
|
|
+ }
|
|
|
+ if modelName != "" {
|
|
|
+ tx = tx.Where("model_name LIKE ?", "%"+modelName+"%")
|
|
|
+ }
|
|
|
+ if channel != 0 {
|
|
|
+ tx = tx.Where("channel_id = ?", channel)
|
|
|
+ }
|
|
|
+ if group != "" {
|
|
|
+ tx = tx.Where("`group` = ?", group)
|
|
|
+ }
|
|
|
+ if startTimestamp != 0 {
|
|
|
+ tx = tx.Where("created_at >= ?", startTimestamp)
|
|
|
+ }
|
|
|
+ if endTimestamp != 0 {
|
|
|
+ tx = tx.Where("created_at <= ?", endTimestamp)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按时间粒度分组 - 使用数据库无关的函数
|
|
|
+ var groupBy string
|
|
|
+ switch defaultTime {
|
|
|
+ case "hour":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "username, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d %H:00:00')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "username, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD HH24:00:00')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "username, strftime('%Y-%m-%d %H:00:00', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ case "day":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "username, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "username, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "username, strftime('%Y-%m-%d', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ case "week":
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "username, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%U')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "username, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-IW')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "username, strftime('%Y-%W', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ if common.UsingMySQL {
|
|
|
+ groupBy = "username, DATE_FORMAT(FROM_UNIXTIME(created_at), '%Y-%m-%d')"
|
|
|
+ } else if common.UsingPostgreSQL {
|
|
|
+ groupBy = "username, TO_CHAR(TO_TIMESTAMP(created_at), 'YYYY-MM-DD')"
|
|
|
+ } else {
|
|
|
+ // SQLite
|
|
|
+ groupBy = "username, strftime('%Y-%m-%d', datetime(created_at, 'unixepoch'))"
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按用户和时间分组
|
|
|
+ tx = tx.Group(groupBy).Order("MIN(created_at) ASC")
|
|
|
+
|
|
|
+ // 执行查询
|
|
|
+ var results []struct {
|
|
|
+ CreatedAt int64 `json:"created_at"`
|
|
|
+ Username string `json:"username"`
|
|
|
+ Count int `json:"count"`
|
|
|
+ Quota int `json:"quota"`
|
|
|
+ }
|
|
|
+
|
|
|
+ err := tx.Scan(&results).Error
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化时间并构建返回结果
|
|
|
+ for _, result := range results {
|
|
|
+ timeStr := formatTime(result.CreatedAt, defaultTime)
|
|
|
+ statistics = append(statistics, UserStatistics{
|
|
|
+ Time: timeStr,
|
|
|
+ Username: result.Username,
|
|
|
+ Quota: result.Quota,
|
|
|
+ Count: result.Count,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return statistics, nil
|
|
|
+}
|
|
|
+
|
|
|
+// formatTime 根据时间粒度格式化时间
|
|
|
+func formatTime(timestamp int64, defaultTime string) string {
|
|
|
+ t := time.Unix(timestamp, 0)
|
|
|
+ switch defaultTime {
|
|
|
+ case "hour":
|
|
|
+ return t.Format("2006-01-02 15:00")
|
|
|
+ case "day":
|
|
|
+ return t.Format("2006-01-02")
|
|
|
+ case "week":
|
|
|
+ _, week := t.ISOWeek()
|
|
|
+ return t.Format("2006-01") + "-W" + strconv.Itoa(week)
|
|
|
+ default:
|
|
|
+ return t.Format("2006-01-02")
|
|
|
+ }
|
|
|
+}
|