Files
web/server/handlers/chunk_upload_cleanup_config.go
whm 0800982224 feat: 分片上传断点续传、临时目录后台配置与清扫、宇恒云账号管理
- 管理端大文件分片上传与 sessionStorage 续传;Nginx 大请求体/超时
- .chunk-uploads 定期清扫;system_config 后台配置保留时长与扫描间隔
- 宇恒云 POST /register 对接与 yuheng_cloud_register_records 留痕;yuheng_cloud:manage 权限

Made-with: Cursor
2026-04-13 14:50:27 +08:00

164 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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": "保留时长须在 6336 小时之间"})
return
}
if input.SweepMinutes < 5 || input.SweepMinutes > 1440 {
c.JSON(http.StatusBadRequest, gin.H{"error": "扫描间隔须在 51440 分钟之间"})
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": "配置已保存"})
}