package handlers import ( "context" "net/http" "os" "strconv" "strings" "time" "yh_web/server/config" "github.com/gin-gonic/gin" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) const chunkCleanupConfigID = "chunk_upload_cleanup" type chunkCleanupConfigDoc struct { MaxAgeHours float64 `bson:"max_age_hours" json:"max_age_hours"` SweepMinutes int `bson:"sweep_minutes" json:"sweep_minutes"` } func maxAgeFromEnv() time.Duration { h := strings.TrimSpace(os.Getenv("YH_CHUNK_UPLOAD_MAX_AGE_HOURS")) if h == "" { return 72 * time.Hour } v, err := strconv.ParseFloat(h, 64) if err != nil || v < 6 { return 72 * time.Hour } return time.Duration(v * float64(time.Hour)) } func sweepFromEnv() time.Duration { m := strings.TrimSpace(os.Getenv("YH_CHUNK_UPLOAD_SWEEP_MINUTES")) if m == "" { return time.Hour } v, err := strconv.Atoi(m) if err != nil || v < 5 { return time.Hour } return time.Duration(v) * time.Minute } func normalizeMaxAgeHours(h float64) time.Duration { if h < 6 { return 6 * time.Hour } if h > 336 { return 336 * time.Hour } return time.Duration(h * float64(time.Hour)) } func normalizeSweepMinutes(m int) time.Duration { if m < 5 { return 5 * time.Minute } if m > 1440 { return 1440 * time.Minute } return time.Duration(m) * time.Minute } // loadChunkCleanupParameters 优先读 MongoDB system_config;无文档时用环境变量;用于定时清扫 func loadChunkCleanupParameters() (maxAge time.Duration, sweepEvery time.Duration) { db := config.GetDB(config.DBName) if db != nil { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var doc chunkCleanupConfigDoc err := db.Collection("system_config").FindOne(ctx, bson.M{"_id": chunkCleanupConfigID}).Decode(&doc) if err == nil && doc.MaxAgeHours >= 6 && doc.SweepMinutes >= 5 { return normalizeMaxAgeHours(doc.MaxAgeHours), normalizeSweepMinutes(doc.SweepMinutes) } } return maxAgeFromEnv(), sweepFromEnv() } // GetChunkUploadCleanupConfig 后台读取当前保存的配置(无文档时返回默认值) func GetChunkUploadCleanupConfig(c *gin.Context) { db := config.GetDB(config.DBName) if db == nil { c.JSON(http.StatusOK, chunkCleanupConfigDoc{ MaxAgeHours: 72, SweepMinutes: 60, }) return } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var doc chunkCleanupConfigDoc err := db.Collection("system_config").FindOne(ctx, bson.M{"_id": chunkCleanupConfigID}).Decode(&doc) if err != nil { if err == mongo.ErrNoDocuments { c.JSON(http.StatusOK, chunkCleanupConfigDoc{ MaxAgeHours: 72, SweepMinutes: 60, }) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if doc.MaxAgeHours < 6 { doc.MaxAgeHours = 72 } if doc.SweepMinutes < 5 { doc.SweepMinutes = 60 } c.JSON(http.StatusOK, doc) } // ChunkUploadCleanupUpdateInput 后台保存 type ChunkUploadCleanupUpdateInput struct { MaxAgeHours float64 `json:"max_age_hours" binding:"required"` SweepMinutes int `json:"sweep_minutes" binding:"required"` } // UpdateChunkUploadCleanupConfig 保存分片临时目录保留时长与扫描间隔 func UpdateChunkUploadCleanupConfig(c *gin.Context) { var input ChunkUploadCleanupUpdateInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if input.MaxAgeHours < 6 || input.MaxAgeHours > 336 { c.JSON(http.StatusBadRequest, gin.H{"error": "保留时长须在 6~336 小时之间"}) return } if input.SweepMinutes < 5 || input.SweepMinutes > 1440 { c.JSON(http.StatusBadRequest, gin.H{"error": "扫描间隔须在 5~1440 分钟之间"}) return } db := config.GetDB(config.DBName) if db == nil { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "数据库未连接,无法保存"}) return } ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() coll := db.Collection("system_config") set := bson.M{ "_id": chunkCleanupConfigID, "max_age_hours": input.MaxAgeHours, "sweep_minutes": input.SweepMinutes, "updated_at": time.Now().Format(time.RFC3339), "updated_by_hint": "admin", } opts := options.UpdateOne().SetUpsert(true) _, err := coll.UpdateOne(ctx, bson.M{"_id": chunkCleanupConfigID}, bson.M{"$set": set}, opts) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"message": "配置已保存"}) }