先看效果:
代码如下:
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 停止服务器