mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
6a8f9a7aea
- Use RWMutex for disk cache reads and singleflight for WebP cache misses - Cache migration read-only state and file access whitelist with pub/sub invalidation - Add w_upload_stats incremental counters updated on upload/delete - Add w_uploads composite indexes and goose backfill migrations - Document performance analysis in docs/PERFORMANCE.md - Fix RegisterCustomRoutes to accept apiV1Router parameter
177 lines
4.7 KiB
Go
177 lines
4.7 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package upload
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/db"
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/common/response"
|
|
)
|
|
|
|
const (
|
|
catImage = "图片"
|
|
catVideo = "视频"
|
|
catAudio = "音频"
|
|
catDocument = "文档"
|
|
catArchive = "压缩包"
|
|
catOther = "其他"
|
|
)
|
|
|
|
type trendItem struct {
|
|
Date string `json:"date"`
|
|
Count int64 `json:"count"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
type distributionItem struct {
|
|
Name string `json:"name"`
|
|
Count int64 `json:"count"`
|
|
Size int64 `json:"size"`
|
|
}
|
|
|
|
type fileStatsResponse struct {
|
|
TotalCount int64 `json:"total_count"`
|
|
TotalSize int64 `json:"total_size"`
|
|
Trend []trendItem `json:"trend"`
|
|
Categories []distributionItem `json:"categories"`
|
|
Types []distributionItem `json:"types"`
|
|
}
|
|
|
|
// GetFileStats 获取系统上传的文件统计数据
|
|
// @Summary 获取文件统计数据
|
|
// @Description 返回系统级的总文件数、占用大小、最近 7 天新增趋势、文件类型/格式分布等数据
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security SessionCookie
|
|
// @Success 200 {object} response.Any{data=fileStatsResponse} "获取成功"
|
|
// @Failure 401 {object} response.Any "未登录"
|
|
// @Failure 403 {object} response.Any "无管理员权限"
|
|
// @Failure 500 {object} response.Any "内部错误"
|
|
// @Router /api/v1/admin/uploads/stats [get]
|
|
func GetFileStats(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
|
|
var stats []model.UploadStat
|
|
if err := db.DB(ctx).Find(&stats).Error; err != nil {
|
|
c.JSON(http.StatusOK, response.Err(err.Error()))
|
|
return
|
|
}
|
|
|
|
now := time.Now()
|
|
trendDates := make([]string, 0, fileStatsTrendDays)
|
|
trendCountMap := make(map[string]int64, fileStatsTrendDays)
|
|
trendSizeMap := make(map[string]int64, fileStatsTrendDays)
|
|
for i := fileStatsTrendDays - 1; i >= 0; i-- {
|
|
date := now.AddDate(0, 0, -i).Format("2006-01-02")
|
|
trendDates = append(trendDates, date)
|
|
trendCountMap[date] = 0
|
|
trendSizeMap[date] = 0
|
|
}
|
|
|
|
var (
|
|
totalCount int64
|
|
totalSize int64
|
|
types []distributionItem
|
|
categories []distributionItem
|
|
)
|
|
|
|
categoriesList := []string{catImage, catVideo, catAudio, catDocument, catArchive, catOther}
|
|
categoryMap := make(map[string]distributionItem, len(categoriesList))
|
|
for _, cat := range categoriesList {
|
|
categoryMap[cat] = distributionItem{Name: cat}
|
|
}
|
|
|
|
for _, stat := range stats {
|
|
switch stat.Dimension {
|
|
case model.UploadStatDimensionTotal:
|
|
totalCount = stat.FileCount
|
|
totalSize = stat.FileSize
|
|
case model.UploadStatDimensionType:
|
|
types = append(types, distributionItem{
|
|
Name: stat.StatKey,
|
|
Count: stat.FileCount,
|
|
Size: stat.FileSize,
|
|
})
|
|
case model.UploadStatDimensionCategory:
|
|
if item, ok := categoryMap[stat.StatKey]; ok {
|
|
item.Count = stat.FileCount
|
|
item.Size = stat.FileSize
|
|
categoryMap[stat.StatKey] = item
|
|
}
|
|
case model.UploadStatDimensionTrend:
|
|
if _, ok := trendCountMap[stat.StatKey]; ok {
|
|
trendCountMap[stat.StatKey] = stat.FileCount
|
|
trendSizeMap[stat.StatKey] = stat.FileSize
|
|
}
|
|
}
|
|
}
|
|
|
|
categories = make([]distributionItem, 0, len(categoriesList))
|
|
for _, cat := range categoriesList {
|
|
categories = append(categories, categoryMap[cat])
|
|
}
|
|
|
|
trend := make([]trendItem, 0, len(trendDates))
|
|
for _, date := range trendDates {
|
|
trend = append(trend, trendItem{
|
|
Date: date,
|
|
Count: trendCountMap[date],
|
|
Size: trendSizeMap[date],
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response.OK(fileStatsResponse{
|
|
TotalCount: totalCount,
|
|
TotalSize: totalSize,
|
|
Trend: trend,
|
|
Categories: categories,
|
|
Types: types,
|
|
}))
|
|
}
|
|
|
|
func getFileCategory(mimeType, ext string) string {
|
|
mimeType = strings.ToLower(mimeType)
|
|
ext = strings.ToLower(ext)
|
|
|
|
if strings.HasPrefix(mimeType, "image/") || isImageExtension(ext) {
|
|
return catImage
|
|
}
|
|
if strings.HasPrefix(mimeType, "video/") {
|
|
return catVideo
|
|
}
|
|
if strings.HasPrefix(mimeType, "audio/") {
|
|
return catAudio
|
|
}
|
|
if isArchiveExtension(ext) || strings.Contains(mimeType, "zip") || strings.Contains(mimeType, "tar") || strings.Contains(mimeType, "gzip") {
|
|
return catArchive
|
|
}
|
|
if isDocumentExtension(ext) || strings.HasPrefix(mimeType, "text/") || mimeType == "application/pdf" {
|
|
return catDocument
|
|
}
|
|
return catOther
|
|
}
|
|
|
|
func isArchiveExtension(ext string) bool {
|
|
for _, e := range []string{"zip", "rar", "7z", "tar", "gz", "tgz", "bz2", "xz"} {
|
|
if ext == e {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isDocumentExtension(ext string) bool {
|
|
for _, e := range []string{"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "md", "csv", "json", "yaml", "yml", "xml"} {
|
|
if ext == e {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
} |