// 文件目录浏览接口:给页面上"选择目录"用的,只列目录、不列文件。 // 前端那个"选择目录"弹窗就是一层层调这个接口往下翻文件夹的。 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 一次目录浏览的结果: // - current:当前在哪个目录(盘符层时为空) // - parent:上一级目录(在根/盘符层时等于 current) // - drives:Windows 的盘符列表(非 Windows 为空) // - dirs:当前目录下的子目录(不含文件) type FsListResult struct { Current string `json:"current"` Parent string `json:"parent"` Drives []string `json:"drives"` Dirs []FsDir `json:"dirs"` } // FsDir 子目录项。 type FsDir struct { Name string `json:"name"` Path string `json:"path"` } // List 处理 GET /api/fs/list?dir=xxx:列出指定目录下的子目录。 // 没传目录时,Windows 返回盘符列表,其他系统返回根目录。 func (c *FsController) List(ctx *gin.Context) { dir := strings.TrimSpace(ctx.Query("dir")) // 没指定目录:Windows 先让用户选盘符 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, }) }