// Package api 提供服务器文件系统浏览接口,供前端目录选择器使用(只列出目录,不列文件)。 package api import ( "net/http" "os" "path/filepath" "runtime" "sort" "strings" "github.com/gin-gonic/gin" ) // FsController 服务器文件系统浏览控制器 type FsController struct{} // NewFsController 创建文件系统浏览控制器 func NewFsController() *FsController { return &FsController{} } // FsListResult 目录浏览结果 type FsListResult struct { Current string `json:"current"` // 当前目录绝对路径;盘符层为空 Parent string `json:"parent"` // 父目录;根/盘符层时等于 Current Drives []string `json:"drives"` // Windows 盘符列表(非 Windows 为空) Dirs []FsDir `json:"dirs"` // 子目录列表(不含文件) } // FsDir 子目录项 type FsDir struct { Name string `json:"name"` Path string `json:"path"` } // List GET /api/fs/list?dir= 列出指定目录的子目录(不含文件); // dir 为空时返回 Windows 盘符列表或 Unix 根目录。 func (c *FsController) List(ctx *gin.Context) { dir := strings.TrimSpace(ctx.Query("dir")) if dir == "" { if runtime.GOOS == "windows" { Success(ctx, FsListResult{Drives: listLogicalDrives()}) return } dir = string(os.PathSeparator) } abs, err := filepath.Abs(dir) if err != nil { Error(ctx, http.StatusBadRequest, "路径解析失败: "+err.Error()) return } // 目录不存在/不可访问时向上回退到最近的已存在父目录,避免阻塞目录选择器 for { st, statErr := os.Stat(abs) if statErr == nil && st.IsDir() { break } parent := filepath.Dir(abs) if parent == abs { // 已到根仍不存在:回退到盘符层 if runtime.GOOS == "windows" { Success(ctx, FsListResult{Drives: listLogicalDrives()}) return } abs = string(os.PathSeparator) break } abs = parent } entries, err := os.ReadDir(abs) if err != nil { Error(ctx, http.StatusBadRequest, "读取目录失败: "+err.Error()) return } parent := filepath.Dir(abs) if parent == abs { parent = abs } dirs := make([]FsDir, 0, len(entries)) for _, e := range entries { if !e.IsDir() { continue } dirs = append(dirs, FsDir{Name: e.Name(), Path: filepath.Join(abs, e.Name())}) } sort.Slice(dirs, func(i, j int) bool { return strings.ToLower(dirs[i].Name) < strings.ToLower(dirs[j].Name) }) Success(ctx, FsListResult{ Current: abs, Parent: parent, Drives: nil, Dirs: dirs, }) }