- 管理端大文件分片上传与 sessionStorage 续传;Nginx 大请求体/超时 - .chunk-uploads 定期清扫;system_config 后台配置保留时长与扫描间隔 - 宇恒云 POST /register 对接与 yuheng_cloud_register_records 留痕;yuheng_cloud:manage 权限 Made-with: Cursor
164 lines
4.5 KiB
Go
164 lines
4.5 KiB
Go
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": "配置已保存"})
|
||
}
|