package main import ( "flag" "log" "net/http" "strings" "time" "github.com/gin-gonic/gin" ) // ----------------------------------------------------------------------------- // 配置 (可被命令行参数覆盖) // ----------------------------------------------------------------------------- var ( uploadDir = flag.String("dir", "./data/uploads", "上传文件保存目录") listen = flag.String("listen", ":8080", "HTTP 监听地址") fileTTL = flag.Duration("ttl", 24*time.Hour, "文件过期 TTL") scanEvery = flag.Duration("scan", 1*time.Hour, "清理扫描间隔") auditDir = flag.String("audit-dir", "./data/audit", "审计日志目录 (每天一个 YYYY-MM-DD.log 文件, 空字符串禁用)") quota = flag.Int64("quota", 10<<30, "总目录配额 (字节, 默认 10GB, 0=不限)") ) const ( fieldName = "file" ) // ----------------------------------------------------------------------------- // main // ----------------------------------------------------------------------------- func main() { flag.Parse() startCleaner() if err := initAudit(); err != nil { log.Fatalf("init audit: %v", err) } initQuota() r := gin.New() r.Use(gin.Logger(), gin.Recovery(), auditT0()) // 嵌入式首页 (HTML 骨架, CSS/JS 经 /static 引用) r.GET("/", func(c *gin.Context) { c.Data(http.StatusOK, "text/html; charset=utf-8", indexTpl) }) // 静态资源 (CSS/JS): no-cache 防止二进制升级后浏览器使用旧缓存 JS r.Use(func(c *gin.Context) { if strings.HasPrefix(c.Request.URL.Path, "/static") { c.Header("Cache-Control", "no-cache") } c.Next() }) r.StaticFS("/static", http.FS(staticFS)) // 业务路由 r.POST("/upload", uploadHandler) r.GET("/download/:filename", downloadHandler) r.POST("/download-zip", batchDownloadHandler) r.POST("/files-delete", batchDeleteHandler) r.DELETE("/files/:filename", deleteHandler) r.GET("/files", listHandler) log.Printf("server listening on %s, uploads -> %s, ttl=%s", *listen, *uploadDir, *fileTTL) if err := r.Run(*listen); err != nil { log.Fatal(err) } }