init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
99
webapp-back/admin/upload.go
Normal file
99
webapp-back/admin/upload.go
Normal file
@@ -0,0 +1,99 @@
|
||||
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"})
|
||||
}
|
||||
Reference in New Issue
Block a user