先看效果:
代碼如下:
package mainimport ("fmt""html/template""log""net/http""os""path/filepath""strings"
)// 配置根目錄(根據需求修改)
//var baseDir = filepath.Join(os.Getenv("/")) // 用戶主目錄
// var baseDir = "C:\\" // Windows系統使用C盤根目錄
var baseDir = "/" // Linux/Mac使用系統根目錄// 文件信息結構體
type FileInfo struct {Name stringPath stringIsDir bool
}// 頁面數據
type PageData struct {Path stringParentDir stringFiles []FileInfo
}// 自定義模板函數
var templateFuncs = template.FuncMap{"splitPath": func(path string) []string {return strings.Split(path, string(filepath.Separator))},"joinPath": func(parts ...string) string {return filepath.Join(parts...)},"slicePath": func(path string, index int) string {parts := strings.Split(path, string(filepath.Separator))return filepath.Join(parts[:index]...)},
}func main() {// 設置路由http.HandleFunc("/", fileHandler)// 啟動服務器fmt.Printf("文件管理服務器已啟動,訪問 http://localhost:8000/\n")fmt.Printf("根目錄: %s\n", baseDir)fmt.Println("按 Ctrl+C 停止服務器")log.Fatal(http.ListenAndServe(":8000", nil))
}func fileHandler(w http.ResponseWriter, r *http.Request) {// 獲取路徑參數path := r.URL.Query().Get("path")fullPath := filepath.Join(baseDir, path)// 安全檢查:確保路徑在baseDir下if !strings.HasPrefix(filepath.Clean(fullPath), filepath.Clean(baseDir)) {http.Error(w, "禁止訪問", http.StatusForbidden)return}// 檢查路徑是否存在fileInfo, err := os.Stat(fullPath)if err != nil {if os.IsNotExist(err) {http.Error(w, "文件不存在", http.StatusNotFound)} else {http.Error(w, "無法訪問文件", http.StatusInternalServerError)}return}// 如果是文件,直接提供下載if !fileInfo.IsDir() {http.ServeFile(w, r, fullPath)return}// 如果是目錄,列出內容dirEntries, err := os.ReadDir(fullPath)if err != nil {http.Error(w, "無法讀取目錄", http.StatusInternalServerError)return}// 準備文件列表var files []FileInfo// 添加上級目錄鏈接(如果不是根目錄)if path != "" {parentDir := filepath.Dir(path)if parentDir == path {parentDir = ""}files = append(files, FileInfo{Name: ".. (上級目錄)",Path: parentDir,IsDir: true,})}// 添加目錄和文件for _, entry := range dirEntries {entryPath := filepath.Join(path, entry.Name())files = append(files, FileInfo{Name: entry.Name(),Path: entryPath,IsDir: entry.IsDir(),})}// 準備模板數據data := PageData{Path: path,ParentDir: filepath.Dir(path),Files: files,}// 創建帶有自定義函數的模板tmpl := template.New("filelist").Funcs(templateFuncs)// 解析模板tmpl, err = tmpl.Parse(htmlTemplate)if err != nil {http.Error(w, "模板錯誤: "+err.Error(), http.StatusInternalServerError)return}// 執行模板err = tmpl.Execute(w, data)if err != nil {http.Error(w, "模板渲染錯誤: "+err.Error(), http.StatusInternalServerError)}
}// HTML模板(移除了非ASCII字符)
const htmlTemplate = `
<!DOCTYPE html>
<html>
<head><title>文件管理器 - {{.Path}}</title><style>body { font-family: Arial, sans-serif; margin: 20px; }h1 { color: #333; }ul { list-style-type: none; padding: 0; }li { padding: 5px 0; }a { text-decoration: none; color: #0066cc; }a:hover { text-decoration: underline; }.file { color: #666; }.dir { color: #009933; font-weight: bold; }.breadcrumb { margin-bottom: 20px; }</style>
</head>
<body><h1>文件管理器</h1><div class="breadcrumb"><a href="/?path=">根目錄</a>{{if .Path}}{{range $i, $part := splitPath .Path}}/ <a href="/?path={{joinPath (slicePath $.Path $i) $part}}">{{$part}}</a>{{end}}{{end}}</div><ul>{{range .Files}}<li><a href="/?path={{.Path}}" class="{{if .IsDir}}dir{{else}}file{{end}}">{{if .IsDir}}[DIR]{{else}}[FILE]{{end}} {{.Name}}</a></li>{{end}}</ul>
</body>
</html>
`
啟動服務:
[root@localhost test]# go run file.go
文件管理服務器已啟動,訪問 http://localhost:8000/
根目錄: /
按 Ctrl+C 停止服務器