包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
100 lines
2.4 KiB
Go
100 lines
2.4 KiB
Go
package admin
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
var allowedCategories = map[string]bool{
|
|
"reports": true,
|
|
"custody": true,
|
|
"links": true,
|
|
}
|
|
|
|
var allowedExts = map[string]bool{
|
|
".pdf": true,
|
|
".png": true,
|
|
".jpg": true,
|
|
".jpeg": true,
|
|
".doc": true,
|
|
".docx": true,
|
|
".txt": true,
|
|
".xls": true,
|
|
".xlsx": true,
|
|
}
|
|
|
|
// UploadFile handles multipart file uploads, saves to ./uploads/{category}/, returns accessible URL.
|
|
// Query param: category (reports|custody), defaults to "reports".
|
|
func UploadFile(c *gin.Context) {
|
|
file, err := c.FormFile("file")
|
|
if err != nil {
|
|
Fail(c, http.StatusBadRequest, "No file provided")
|
|
return
|
|
}
|
|
|
|
ext := strings.ToLower(filepath.Ext(file.Filename))
|
|
if !allowedExts[ext] {
|
|
Fail(c, http.StatusBadRequest, "File type not allowed")
|
|
return
|
|
}
|
|
|
|
category := c.DefaultQuery("category", "reports")
|
|
if !allowedCategories[category] {
|
|
Fail(c, http.StatusBadRequest, "Invalid category")
|
|
return
|
|
}
|
|
|
|
dir := "uploads/" + category
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
Fail(c, http.StatusInternalServerError, "Failed to create upload directory")
|
|
return
|
|
}
|
|
|
|
filename := fmt.Sprintf("%d_%s", time.Now().UnixNano(), filepath.Base(file.Filename))
|
|
dst := filepath.Join(dir, filename)
|
|
|
|
if err := c.SaveUploadedFile(file, dst); err != nil {
|
|
Fail(c, http.StatusInternalServerError, "Failed to save file")
|
|
return
|
|
}
|
|
|
|
url := "/uploads/" + category + "/" + filename
|
|
OK(c, gin.H{"url": url})
|
|
}
|
|
|
|
// DeleteUploadedFile deletes a previously uploaded file by its URL path.
|
|
func DeleteUploadedFile(c *gin.Context) {
|
|
urlPath := c.Query("path") // e.g. /uploads/reports/xxx.pdf or /uploads/custody/xxx.pdf
|
|
if !strings.HasPrefix(urlPath, "/uploads/") {
|
|
Fail(c, http.StatusBadRequest, "Invalid file path")
|
|
return
|
|
}
|
|
if strings.Contains(urlPath, "..") {
|
|
Fail(c, http.StatusBadRequest, "Invalid file path")
|
|
return
|
|
}
|
|
// Verify the second segment is a known category
|
|
parts := strings.SplitN(strings.TrimPrefix(urlPath, "/uploads/"), "/", 2)
|
|
if len(parts) != 2 || !allowedCategories[parts[0]] {
|
|
Fail(c, http.StatusBadRequest, "Invalid file path")
|
|
return
|
|
}
|
|
|
|
localPath := strings.TrimPrefix(urlPath, "/")
|
|
if err := os.Remove(localPath); err != nil {
|
|
if os.IsNotExist(err) {
|
|
OK(c, gin.H{"message": "file not found, skipped"})
|
|
return
|
|
}
|
|
Fail(c, http.StatusInternalServerError, "Failed to delete file")
|
|
return
|
|
}
|
|
OK(c, gin.H{"message": "deleted"})
|
|
}
|