先看效果:

代码如下:
Go
package main
import (
"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 string
Path string
IsDir bool
}
// 页面数据
type PageData struct {
Path string
ParentDir string
Files []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>
`
启动服务:
bash
[root@localhost test]# go run file.go
文件管理服务器已启动,访问 http://localhost:8000/
根目录: /
按 Ctrl+C 停止服务器