Initial commit: File manager with upload, search, and modal dialogs
This commit is contained in:
714
main.go
Normal file
714
main.go
Normal file
@@ -0,0 +1,714 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type FileInfo struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
IsDir bool `json:"isDir"`
|
||||
Size int64 `json:"size"`
|
||||
ModTime time.Time `json:"modTime"`
|
||||
CanPreview bool `json:"canPreview"`
|
||||
CanDownload bool `json:"canDownload"`
|
||||
}
|
||||
|
||||
type UploadResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type MoveRequest struct {
|
||||
SrcPath string `json:"srcPath"`
|
||||
DestPath string `json:"destPath"`
|
||||
}
|
||||
|
||||
type RenameRequest struct {
|
||||
OldPath string `json:"oldPath"`
|
||||
NewName string `json:"newName"`
|
||||
}
|
||||
|
||||
type CreateDirRequest struct {
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type WatchEvent struct {
|
||||
Type string `json:"type"`
|
||||
Path string `json:"path"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
var (
|
||||
rootDir string
|
||||
watcher *fsnotify.Watcher
|
||||
watchChan chan WatchEvent
|
||||
)
|
||||
|
||||
func init() {
|
||||
watchChan = make(chan WatchEvent, 100)
|
||||
}
|
||||
|
||||
func getFileType(filename string) string {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
switch ext {
|
||||
case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp":
|
||||
return "image"
|
||||
case ".txt", ".md", ".json", ".xml", ".html", ".css", ".js", ".go", ".py", ".java", ".c", ".cpp", ".h", ".sh":
|
||||
return "text"
|
||||
case ".pdf":
|
||||
return "pdf"
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
func formatFileSize(size int64) string {
|
||||
const unit = 1024
|
||||
if size < unit {
|
||||
return fmt.Sprintf("%d B", size)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := size / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(size)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
func listFiles(c *gin.Context) {
|
||||
path := c.Query("path")
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(rootDir, path)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的路径"})
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
entries, err := file.Readdir(-1)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var files []FileInfo
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if name == "." || name == ".." || name == "static" {
|
||||
continue
|
||||
}
|
||||
|
||||
entryPath := filepath.Join(path, name)
|
||||
|
||||
fileType := getFileType(name)
|
||||
canPreview := false
|
||||
if !entry.IsDir() {
|
||||
canPreview = fileType == "image" || fileType == "text" || fileType == "pdf"
|
||||
}
|
||||
|
||||
files = append(files, FileInfo{
|
||||
Name: name,
|
||||
Path: entryPath,
|
||||
IsDir: entry.IsDir(),
|
||||
Size: entry.Size(),
|
||||
ModTime: entry.ModTime(),
|
||||
CanPreview: canPreview,
|
||||
CanDownload: true,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"files": files,
|
||||
"path": path,
|
||||
})
|
||||
}
|
||||
|
||||
func previewFile(c *gin.Context) {
|
||||
path := c.Query("path")
|
||||
if path == "" {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "缺少path参数"})
|
||||
return
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(rootDir, path)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的路径"})
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
fileType := getFileType(info.Name())
|
||||
|
||||
if fileType == "image" {
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
contentType := http.DetectContentType(data)
|
||||
c.Data(http.StatusOK, contentType, data)
|
||||
} else if fileType == "text" {
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.Data(http.StatusOK, "text/plain; charset=utf-8", data)
|
||||
} else if fileType == "pdf" {
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
c.Data(http.StatusOK, "application/pdf", data)
|
||||
} else {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "不支持预览此类型文件"})
|
||||
}
|
||||
}
|
||||
|
||||
func downloadFile(c *gin.Context) {
|
||||
pathsParam := c.Query("paths")
|
||||
if pathsParam == "" {
|
||||
path := c.Query("path")
|
||||
if path == "" {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "缺少path参数"})
|
||||
return
|
||||
}
|
||||
downloadSingle(c, path)
|
||||
return
|
||||
}
|
||||
|
||||
var paths []string
|
||||
if err := json.Unmarshal([]byte(pathsParam), &paths); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的paths参数"})
|
||||
return
|
||||
}
|
||||
|
||||
if len(paths) == 1 {
|
||||
downloadSingle(c, paths[0])
|
||||
return
|
||||
}
|
||||
|
||||
downloadMultiple(c, paths)
|
||||
}
|
||||
|
||||
func downloadSingle(c *gin.Context, path string) {
|
||||
fullPath := filepath.Join(rootDir, path)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的路径"})
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
downloadDirectory(c, path)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", info.Name()))
|
||||
c.Header("Content-Type", "application/octet-stream")
|
||||
c.Header("Content-Length", fmt.Sprintf("%d", info.Size()))
|
||||
io.Copy(c.Writer, file)
|
||||
}
|
||||
|
||||
func downloadDirectory(c *gin.Context, path string) {
|
||||
fullPath := filepath.Join(rootDir, path)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的路径"})
|
||||
return
|
||||
}
|
||||
|
||||
dirName := filepath.Base(path)
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip", dirName))
|
||||
c.Header("Content-Type", "application/zip")
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
|
||||
zw := zip.NewWriter(c.Writer)
|
||||
defer zw.Close()
|
||||
|
||||
filepath.Walk(fullPath, func(filePath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
relPath, _ := filepath.Rel(rootDir, filePath)
|
||||
if relPath == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
header.Name = relPath
|
||||
header.Method = zip.Deflate
|
||||
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
} else {
|
||||
writer, err := zw.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
io.Copy(writer, file)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func downloadMultiple(c *gin.Context, paths []string) {
|
||||
c.Header("Content-Disposition", "attachment; filename=files.zip")
|
||||
c.Header("Content-Type", "application/zip")
|
||||
c.Header("Content-Transfer-Encoding", "binary")
|
||||
|
||||
zw := zip.NewWriter(c.Writer)
|
||||
defer zw.Close()
|
||||
|
||||
for _, path := range paths {
|
||||
fullPath := filepath.Join(rootDir, path)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
filepath.Walk(fullPath, func(filePath string, fileInfo os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
relPath, _ := filepath.Rel(rootDir, filePath)
|
||||
header, err := zip.FileInfoHeader(fileInfo)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
header.Name = relPath
|
||||
header.Method = zip.Deflate
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
header.Name += "/"
|
||||
} else {
|
||||
writer, err := zw.CreateHeader(header)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
io.Copy(writer, file)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
file, err := os.Open(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
header.Name = filepath.Base(path)
|
||||
header.Method = zip.Deflate
|
||||
|
||||
writer, err := zw.CreateHeader(header)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
io.Copy(writer, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func uploadFile(c *gin.Context) {
|
||||
path := c.DefaultQuery("path", "")
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, UploadResponse{
|
||||
Success: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
fullPath := filepath.Join(rootDir, path, header.Filename)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, UploadResponse{
|
||||
Success: false,
|
||||
Message: "无效的路径",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
outFile, err := os.Create(fullPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, UploadResponse{
|
||||
Success: false,
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
io.Copy(outFile, file)
|
||||
|
||||
watchChan <- WatchEvent{Type: "create", Path: fullPath, Name: header.Filename}
|
||||
|
||||
c.JSON(http.StatusOK, UploadResponse{
|
||||
Success: true,
|
||||
Message: "上传成功",
|
||||
})
|
||||
}
|
||||
|
||||
func deleteFiles(c *gin.Context) {
|
||||
var paths []string
|
||||
if err := c.ShouldBindJSON(&paths); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的请求体"})
|
||||
return
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
fullPath := filepath.Join(rootDir, path)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
os.RemoveAll(fullPath)
|
||||
} else {
|
||||
os.Remove(fullPath)
|
||||
}
|
||||
|
||||
watchChan <- WatchEvent{Type: "delete", Path: fullPath, Name: filepath.Base(path)}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
||||
func moveFile(c *gin.Context) {
|
||||
var req MoveRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的请求体"})
|
||||
return
|
||||
}
|
||||
|
||||
srcPath := filepath.Join(rootDir, req.SrcPath)
|
||||
destPath := filepath.Join(rootDir, req.DestPath)
|
||||
|
||||
if !strings.HasPrefix(srcPath, rootDir) || !strings.HasPrefix(destPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的路径"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Rename(srcPath, destPath); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
watchChan <- WatchEvent{Type: "move", Path: destPath, Name: filepath.Base(destPath)}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
||||
func renameFile(c *gin.Context) {
|
||||
var req RenameRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的请求体"})
|
||||
return
|
||||
}
|
||||
|
||||
srcPath := filepath.Join(rootDir, req.OldPath)
|
||||
newPath := filepath.Join(rootDir, filepath.Dir(req.OldPath), req.NewName)
|
||||
|
||||
if !strings.HasPrefix(srcPath, rootDir) || !strings.HasPrefix(newPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的路径"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Rename(srcPath, newPath); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
watchChan <- WatchEvent{Type: "rename", Path: newPath, Name: req.NewName}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
||||
func createDir(c *gin.Context) {
|
||||
var req CreateDirRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的请求体"})
|
||||
return
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(rootDir, req.Path, req.Name)
|
||||
if !strings.HasPrefix(fullPath, rootDir) {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "无效的路径"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(fullPath, 0755); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ErrorResponse{Error: err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
watchChan <- WatchEvent{Type: "create", Path: fullPath, Name: req.Name}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
||||
}
|
||||
|
||||
func watchFiles(c *gin.Context) {
|
||||
c.Header("Content-Type", "text/event-stream")
|
||||
c.Header("Cache-Control", "no-cache")
|
||||
c.Header("Connection", "keep-alive")
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
|
||||
c.Writer.Flush()
|
||||
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-watchChan:
|
||||
data, _ := json.Marshal(event)
|
||||
c.SSEvent("message", string(data))
|
||||
c.Writer.Flush()
|
||||
case <-ticker.C:
|
||||
c.SSEvent("ping", time.Now().Format("2006-01-02 15:04:05"))
|
||||
c.Writer.Flush()
|
||||
case <-c.Request.Context().Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func startWatcher() {
|
||||
var err error
|
||||
watcher, err = fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
fmt.Printf("警告: 无法创建文件监控器: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Op&fsnotify.Create == fsnotify.Create ||
|
||||
event.Op&fsnotify.Write == fsnotify.Write ||
|
||||
event.Op&fsnotify.Remove == fsnotify.Remove ||
|
||||
event.Op&fsnotify.Rename == fsnotify.Rename ||
|
||||
event.Op&fsnotify.Chmod == fsnotify.Chmod {
|
||||
|
||||
relPath, _ := filepath.Rel(rootDir, event.Name)
|
||||
if relPath == "." {
|
||||
continue
|
||||
}
|
||||
|
||||
eventType := "modify"
|
||||
if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
eventType = "create"
|
||||
} else if event.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
eventType = "delete"
|
||||
} else if event.Op&fsnotify.Rename == fsnotify.Rename {
|
||||
eventType = "rename"
|
||||
}
|
||||
|
||||
select {
|
||||
case watchChan <- WatchEvent{Type: eventType, Path: event.Name, Name: filepath.Base(event.Name)}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fmt.Printf("监控错误: %v\n", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
watcher.Add(rootDir)
|
||||
filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err == nil && info.IsDir() {
|
||||
watcher.Add(path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
port := "8080"
|
||||
dir := "./files"
|
||||
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
switch os.Args[i] {
|
||||
case "-port":
|
||||
if i+1 < len(os.Args) {
|
||||
port = os.Args[i+1]
|
||||
i++
|
||||
}
|
||||
case "-root":
|
||||
if i+1 < len(os.Args) {
|
||||
dir = os.Args[i+1]
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(dir) {
|
||||
cwd, _ := os.Getwd()
|
||||
dir = filepath.Join(cwd, dir)
|
||||
}
|
||||
|
||||
rootDir = dir
|
||||
|
||||
if err := os.MkdirAll(rootDir, 0755); err != nil {
|
||||
fmt.Printf("错误: 无法创建根目录: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
router := gin.New()
|
||||
router.Use(gin.Recovery())
|
||||
router.Use(corsMiddleware())
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
c.Header("X-Frame-Options", "DENY")
|
||||
c.Header("X-XSS-Protection", "1; mode=block")
|
||||
|
||||
html, err := os.ReadFile("/var/www/static/index.html")
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "无法加载页面")
|
||||
return
|
||||
}
|
||||
c.String(http.StatusOK, "%s", html)
|
||||
})
|
||||
|
||||
router.Static("/static", "/var/www/static")
|
||||
|
||||
api := router.Group("/api")
|
||||
{
|
||||
api.GET("/files", listFiles)
|
||||
api.GET("/preview", previewFile)
|
||||
api.GET("/download", downloadFile)
|
||||
api.POST("/upload", uploadFile)
|
||||
api.DELETE("/files", deleteFiles)
|
||||
api.PUT("/move", moveFile)
|
||||
api.PUT("/rename", renameFile)
|
||||
api.POST("/dir", createDir)
|
||||
api.GET("/watch", watchFiles)
|
||||
}
|
||||
|
||||
go startWatcher()
|
||||
|
||||
addr := fmt.Sprintf(":%s", port)
|
||||
fmt.Printf("文件管理器已启动: http://localhost:%s\n", port)
|
||||
fmt.Printf("根目录: %s\n", rootDir)
|
||||
|
||||
if err := router.Run(addr); err != nil {
|
||||
fmt.Printf("错误: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func corsMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user