文章目录
- 我们用 Go 的 map[string]*File 模拟文件目录结构,每个 File 可以是普通文件或目录。整棵树从根节点 / 开始,长得像这样: /└── README.txt 所有操作(创建、读取、删除、重命名)都是在这棵树上"修枝剪叶"。
- 普通 HTTP 只能 GET/POST,而 WebDAV 增加了 MKCOL(建目录)、PUT(上传)、MOVE(重命名)等方法。Go 的 golang.org/x/net/webdav 包帮我们实现了协议解析,我们只需提供底层文件系统的实现。
- 重命名的本质是: 从旧父目录中删掉条目 在新父目录中加上新名字 如果跨目录,还要更新父指针 关键是要同时锁住两个父目录,防止并发时"文件失踪"。我们的代码做到了这一点,安全又高效。
- 程序启动后,偷偷执行: net use Z: http://localhost:8080 /y 于是你的资源管理器就多了一个"网络驱动器"。退出时再执行 /delete,不留痕迹。
- package main import ( “context” “flag” “fmt” “log” “net/http” “os” “os/exec” “os/signal” “sync” “syscall” “time” “golang.org/x/net/webdav” “memdisk/memdisk” ) // 使用Windows net use命令映射网络驱动器 func mapNetworkDrive(serverURL, drive string) { // 首先,如果已经连接则断开连接 exec.Command(“net”, “use”, drive, “/delete”, “/y”).Run() // 然后映射网络驱动器 cmd := exec.Command(“net”, “use”, drive, serverURL) if err := cmd.Run(); err != nil { log.Printf(“映射网络驱动器失败: %v”, err) // 尝试不同的方法 log.Printf(“您可以手动映射驱动器: net use %s %s”, drive, serverURL) } else { log.Printf(“成功将 %s 映射到 %s”, drive, serverURL) } } // 使用Windows net use命令取消映射网络驱动器 func unmapNetworkDrive(drive string) { log.Printf(“正在取消映射网络驱动器: %s”, drive) cmd := exec.Command(“net”, “use”, drive, “/delete”, “/y”) if err := cmd.Run(); err != nil { log.Printf(“取消映射网络驱动器失败: %v”, err) } else { log.Printf(“成功取消映射 %s”, drive) } } func main() { // 解析命令行参数 var port string var drive string flag.StringVar(&port, “port”, “8080”, “服务器端口”) flag.StringVar(&drive, “drive”, “Z:”, “映射的网络驱动器盘符”) flag.Parse() // 如果使用旧的参数方式,仍然支持 if flag.NArg() > 0 { port = flag.Arg(0) } if flag.NArg() > 1 { drive = flag.Arg(1) } // 创建一个新的内存文件系统 fs := memdisk.NewMemFS() // 只创建一个README文件 fs.WriteFile(“/README.txt”, []byte(“内存文件系统rnrn这是一个运行在内存中的文件服务器。rn所有文件都存储在RAM中,服务器停止时将丢失。rnJjMgx”)) // 用于网络驱动器映射的服务器URL serverURL := fmt.Sprintf(“http://localhost:%s”, port) // 启动WebDAV服务器 startWebDAVServer(fs, port, drive, serverURL) } // 启动WebDAV服务器 func startWebDAVServer(fs *memdisk.MemFS, port, drive, serverURL string) { // 创建WebDAV文件系统 wdFs := memdisk.NewWebDAVFS(fs) // 创建WebDAV处理器 handler := &webdav.Handler{ FileSystem: wdFs, LockSystem: webdav.NewMemLS(), Logger: func(r *http.Request, err error) { if err != nil { log.Printf(“WebDAV错误: %v %v”, r.Method, err) } else { log.Printf(“WebDAV请求: %v %v”, r.Method, r.URL) } }, } // 创建服务器多路复用器 mux := http.NewServeMux() // 为所有路径注册WebDAV处理器 mux.HandleFunc(“/”, func(w http.ResponseWriter, r *http.Request) { // 添加CORS头部 w.Header().Set(“Access-Control-Allow-Origin”, “*”) w.Header().Set(“Access-Control-Allow-Methods”, “GET, POST, PUT, DELETE, MKCOL, COPY, MOVE, OPTIONS”) w.Header().Set(“Access-Control-Allow-Headers”, “Content-Type, Depth, Overwrite, Destination, Authorization”) // 添加可能影响显示名称的自定义头部 w.Header().Set(“Server”, “CustomWebDAVServer”) w.Header().Set(“X-Server-Name”, “MemoryFileSystem”) // 处理OPTIONS请求 if r.Method == “OPTIONS” { w.WriteHeader(http.StatusOK) return } // 调用WebDAV处理器 handler.ServeHTTP(w, r) }) // 监听中断信号的通道 sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // 确保清理完成的等待组 var wg sync.WaitGroup // 启动服务器goroutine并在准备好时映射网络驱动器 log.Printf(“正在启动WebDAV服务器于 :%s”, port) log.Printf(“访问服务器地址 http://localhost:%s”, port) log.Printf(“尝试映射为网络驱动器: %s”, drive) // 服务器goroutine server := &http.Server{ Addr: fmt.Sprintf(“:%s”, port), Handler: mux, } go func() { wg.Add(1) defer wg.Done() if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf(“服务器启动失败: %v”, err) } }() // 给服务器一点时间启动,然后映射网络驱动器 go func() { time.Sleep(500 * time.Millisecond) log.Printf(“正在映射网络驱动器: %s 到 %s”, drive, serverURL) mapNetworkDrive(serverURL, drive) }() // 等待中断信号 <-sigChan log.Println(“正在关闭服务器…”) // 取消映射网络驱动器 unmapNetworkDrive(drive) // 优雅地关闭服务器 if err := server.Shutdown(context.Background()); err != nil { log.Printf(“服务器关闭错误: %v”, err) } // 等待所有goroutine完成 wg.Wait() log.Println(“服务器已优雅退出”) }
- package memdisk import ( “os” “sync” “time” ) // File 表示内存文件系统中的一个文件 type File struct { Filename string Content []byte FileSize int64 CreatedAt time.Time ModifiedAt time.Time IsDirectory bool Children map[string]*File Parent *File mu sync.RWMutex } // MemFS 表示内存文件系统 type MemFS struct { Root *File mu sync.RWMutex } // NewMemFS 创建一个新的MemFS实例 func NewMemFS() *MemFS { root := &File{ Filename: “”, IsDirectory: true, Children: make(map[string]*File), CreatedAt: time.Now(), ModifiedAt: time.Now(), } return &MemFS{ Root: root, } } // splitPath 将路径分割为其组成部分 func splitPath(path string) []string { if path == “/” { return []string{} } components := []string{} current := “” for _, c := range path { if c == ‘/’ { if current != “” { components = append(components, current) current = “” } } else { current += string(c) } } if current != “” { components = append(components, current) } return components } // GetFile 根据路径获取文件或目录 func (fs *MemFS) GetFile(path string) (*File, error) { if path == “” { return nil, os.ErrInvalid } fs.mu.RLock() defer fs.mu.RUnlock() components := splitPath(path) current := fs.Root for _, comp := range components { current.mu.RLock() child, exists := current.Children[comp] current.mu.RUnlock() if !exists { return nil, os.ErrNotExist } current = child } return current, nil } // CreateDir 在给定路径中创建新目录 func (fs *MemFS) CreateDir(path string) (*File, error) { if path == “” { return nil, os.ErrInvalid } components := splitPath(path) if len(components) == 0 { return fs.Root, nil // 根目录已存在 } // 在不持有全局锁的情况下获取父级 parentPathComponents := components[:len(components)-1] parentPath := “/” + joinPath(parentPathComponents) parent, err := fs.GetFile(parentPath) if err != nil { return nil, err } fs.mu.Lock() defer fs.mu.Unlock() parent.mu.Lock() defer parent.mu.Unlock() newDirName := components[len(components)-1] if _, exists := parent.Children[newDirName]; exists { return nil, os.ErrExist } newDir := &File{ Filename: newDirName, IsDirectory: true, Children: make(map[string]*File), CreatedAt: time.Now(), ModifiedAt: time.Now(), Parent: parent, } parent.Children[newDirName] = newDir return newDir, nil } // CreateFile 在给定路径中创建新文件 func (fs *MemFS) CreateFile(path string) (*File, error) { if path == “” { return nil, os.ErrInvalid } components := splitPath(path) if len(components) == 0 { return nil, os.ErrInvalid } // 在不持有全局锁的情况下获取父级 parentPathComponents := components[:len(components)-1] parentPath := “/” + joinPath(parentPathComponents) parent, err := fs.GetFile(parentPath) if err != nil { return nil, err } fs.mu.Lock() defer fs.mu.Unlock() parent.mu.Lock() defer parent.mu.Unlock() newFileName := components[len(components)-1] if _, exists := parent.Children[newFileName]; exists { return nil, os.ErrExist } newFile := &File{ Filename: newFileName, IsDirectory: false, Content: []byte{}, FileSize: 0, CreatedAt: time.Now(), ModifiedAt: time.Now(), Parent: parent, } parent.Children[newFileName] = newFile return newFile, nil } // ReadFile 读取文件内容 func (fs *MemFS) ReadFile(path string) ([]byte, error) { file, err := fs.GetFile(path) if err != nil { return nil, err } file.mu.RLock() defer file.mu.RUnlock() if file.IsDirectory { return nil, os.ErrInvalid } content := make([]byte, len(file.Content)) copy(content, file.Content) return content, nil } // WriteFile 将内容写入文件 func (fs *MemFS) WriteFile(path string, content []byte) error { file, err := fs.GetFile(path) if err != nil { // 如果文件不存在则尝试创建 file, err = fs.CreateFile(path) if err != nil { return err } } file.mu.Lock() defer file.mu.Unlock() if file.IsDirectory { return os.ErrInvalid } file.Content = make([]byte, len(content)) copy(file.Content, content) file.FileSize = int64(len(content)) file.ModifiedAt = time.Now() return nil } // Delete 删除文件或目录 func (fs *MemFS) Delete(path string) error { if path == “/” { return os.ErrPermission } components := splitPath(path) if len(components) == 0 { return os.ErrInvalid } // 在不持有全局锁的情况下获取父级 parentPathComponents := components[:len(components)-1] parentPath := “/” + joinPath(parentPathComponents) parent, err := fs.GetFile(parentPath) if err != nil { return err } fs.mu.Lock() defer fs.mu.Unlock() parent.mu.Lock() defer parent.mu.Unlock() fileName := components[len(components)-1] if _, exists := parent.Children[fileName]; !exists { return os.ErrNotExist } delete(parent.Children, fileName) return nil } // Rename 重命名文件或目录 func (fs *MemFS) Rename(oldPath, newPath string) error { if oldPath == “/” || newPath == “/” { return os.ErrPermission } oldComponents := splitPath(oldPath) newComponents := splitPath(newPath) if len(oldComponents) == 0 || len(newComponents) == 0 { return os.ErrInvalid } // 获取旧文件的父目录和文件名 oldParentPathComponents := oldComponents[:len(oldComponents)-1] oldParentPath := “/” + joinPath(oldParentPathComponents) oldParent, err := fs.GetFile(oldParentPath) if err != nil { return err } // 获取新文件的父目录和文件名 newParentPathComponents := newComponents[:len(newComponents)-1] newParentPath := “/” + joinPath(newParentPathComponents) newParent, err := fs.GetFile(newParentPath) if err != nil { return err } // 对相同对象只锁定一次 sameParent := oldParent == newParent fs.mu.Lock() defer fs.mu.Unlock() oldParent.mu.Lock() defer oldParent.mu.Unlock() // 如果不是同一父目录,则锁定新父目录 if !sameParent { newParent.mu.Lock() defer newParent.mu.Unlock() } oldFileName := oldComponents[len(oldComponents)-1] newFileName := newComponents[len(newComponents)-1] // 检查旧文件是否存在 oldFile, exists := oldParent.Children[oldFileName] if !exists { return os.ErrNotExist } // 检查新文件是否已存在 if _, exists := newParent.Children[newFileName]; exists { return os.ErrExist } // 从旧位置移除 delete(oldParent.Children, oldFileName) // 更新文件名 oldFile.Filename = newFileName // 更新父指针(如果不是同一父目录) if !sameParent { oldFile.Parent = newParent } // 添加到新位置 newParent.Children[newFileName] = oldFile // 更新修改时间 oldFile.ModifiedAt = time.Now() return nil } // ListDir 列出目录内容 func (fs *MemFS) ListDir(path string) ([]*File, error) { dir, err := fs.GetFile(path) if err != nil { return nil, err } dir.mu.RLock() defer dir.mu.RUnlock() if !dir.IsDirectory { return nil, os.ErrInvalid } children := make([]*File, 0, len(dir.Children)) for _, child := range dir.Children { child.mu.RLock() // 创建副本以避免竞态条件 copyChild := &File{ Filename: child.Filename, FileSize: child.FileSize, CreatedAt: child.CreatedAt, ModifiedAt: child.ModifiedAt, IsDirectory: child.IsDirectory, } child.mu.RUnlock() children = append(children, copyChild) } return children, nil } // joinPath 将路径组件连接成单个路径字符串 func joinPath(components []string) string { result := “” for i, comp := range components { if i > 0 { result += “/” } result += comp } return result }
- package memdisk import ( “context” “io” “os” “time” “golang.org/x/net/webdav” ) // 为File实现os.FileInfo接口 func (f *File) Name() string { return f.Filename } func (f *File) Size() int64 { return f.FileSize } func (f *File) Mode() os.FileMode { if f.IsDirectory { return 0755 | os.ModeDir } return 0644 } func (f *File) ModTime() time.Time { return f.ModifiedAt } func (f *File) IsDir() bool { return f.IsDirectory } func (f *File) Sys() interface{} { return nil } // … 移除兼容性层,现在直接实现Name()和Size() … // WebDAVFS实现了webdav.FileSystem接口 type WebDAVFS struct { fs *MemFS } // NewWebDAVFS创建一个新的WebDAVFS实例 func NewWebDAVFS(fs *MemFS) *WebDAVFS { return &WebDAVFS{ fs: fs, } } // Mkdir创建一个新目录 func (wfs *WebDAVFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error { _, err := wfs.fs.CreateDir(name) return err } // OpenFile打开一个文件 func (wfs *WebDAVFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) { // 检查文件是否存在 file, err := wfs.fs.GetFile(name) if err != nil { if os.IsNotExist(err) && (flag&os.O_CREATE != 0) { // 如果文件不存在且指定了O_CREATE标志,则创建文件 file, err = wfs.fs.CreateFile(name) if err != nil { return nil, err } } else { return nil, err } } // 返回WebDAVFile包装器 return &WebDAVFile{ file: file, pos: 0, }, nil } // RemoveAll删除文件或目录 func (wfs *WebDAVFS) RemoveAll(ctx context.Context, name string) error { return wfs.fs.Delete(name) } // Rename重命名文件或目录 func (wfs *WebDAVFS) Rename(ctx context.Context, oldName, newName string) error { return wfs.fs.Rename(oldName, newName) } // Stat返回文件信息 func (wfs *WebDAVFS) Stat(ctx context.Context, name string) (os.FileInfo, error) { file, err := wfs.fs.GetFile(name) if err != nil { return nil, err } return file, nil } // WebDAVFile实现了webdav.File接口 type WebDAVFile struct { file *File pos int64 } // Close关闭文件 func (wf *WebDAVFile) Close() error { return nil // 内存文件无需操作 } // Read从文件读取 func (wf *WebDAVFile) Read(p []byte) (n int, err error) { wf.file.mu.RLock() defer wf.file.mu.RUnlock() if wf.pos >= int64(len(wf.file.Content)) { return 0, io.EOF } n = copy(p, wf.file.Content[wf.pos:]) wf.pos += int64(n) return n, nil } // ReadAt在指定偏移位置从文件读取 func (wf *WebDAVFile) ReadAt(p []byte, off int64) (n int, err error) { wf.file.mu.RLock() defer wf.file.mu.RUnlock() if off >= int64(len(wf.file.Content)) { return 0, io.EOF } n = copy(p, wf.file.Content[off:]) if n < len(p) { err = io.EOF } return n, err } // Seek定位到指定位置 func (wf *WebDAVFile) Seek(offset int64, whence int) (int64, error) { wf.file.mu.RLock() defer wf.file.mu.RUnlock() var newPos int64 switch whence { case io.SeekStart: newPos = offset case io.SeekCurrent: newPos = wf.pos + offset case io.SeekEnd: newPos = int64(len(wf.file.Content)) + offset default: return 0, os.ErrInvalid } if newPos < 0 { return 0, os.ErrInvalid } wf.pos = newPos return newPos, nil } // Write向文件写入 func (wf *WebDAVFile) Write(p []byte) (n int, err error) { wf.file.mu.Lock() defer wf.file.mu.Unlock() // 计算写入后的新位置 newPos := wf.pos + int64(len(p)) // 如需要则调整内容大小 if newPos > int64(len(wf.file.Content)) { // 扩展内容 newContent := make([]byte, newPos) copy(newContent, wf.file.Content) wf.file.Content = newContent } // 将p写入当前位置 copy(wf.file.Content[wf.pos:newPos], p) // 更新位置和大小 wf.pos = newPos wf.file.FileSize = newPos wf.file.ModifiedAt = time.Now() return len(p), nil } // WriteAt在指定偏移位置向文件写入 func (wf *WebDAVFile) WriteAt(p []byte, off int64) (n int, err error) { wf.file.mu.Lock() defer wf.file.mu.Unlock() newPos := off + int64(len(p)) if newPos > int64(len(wf.file.Content)) { // 扩展内容 newContent := make([]byte, newPos) copy(newContent, wf.file.Content) wf.file.Content = newContent } copy(wf.file.Content[off:newPos], p) // 如需要则更新大小 if newPos > wf.file.FileSize { wf.file.FileSize = newPos } wf.file.ModifiedAt = time.Now() return len(p), nil } // Readdir读取目录条目 func (wf *WebDAVFile) Readdir(count int) ([]os.FileInfo, error) { wf.file.mu.RLock() defer wf.file.mu.RUnlock() if !wf.file.IsDirectory { return nil, os.ErrInvalid } // 将map值转换为切片 children := make([]os.FileInfo, 0, len(wf.file.Children)) for _, child := range wf.file.Children { children = append(children, child) } // 处理count参数 if count <= 0 { return children, nil } if count > len(children) { return children, io.EOF } return children[:count], nil } // Readdirnames读取目录条目名称 func (wf *WebDAVFile) Readdirnames(n int) ([]string, error) { wf.file.mu.RLock() defer wf.file.mu.RUnlock() if !wf.file.IsDirectory { return nil, os.ErrInvalid } // 将map键转换为切片 names := make([]string, 0, len(wf.file.Children)) for name := range wf.file.Children { names = append(names, name) } // 处理n参数 if n <= 0 { return names, nil } if n > len(names) { return names, io.EOF } return names[:n], nil } // Stat返回文件信息 func (wf *WebDAVFile) Stat() (os.FileInfo, error) { return wf.file, nil }
目录
- 功能特性
- 技术原理
- 1. 内存文件系统 = 一棵树
- 2. WebDAV = HTTP 的"文件操作扩展包"
- 3. 重命名 = "搬家+改名"
- 4. Windows 映射 = net use 命令自动化
- 使用方法
- 第一步:准备环境
- 第二步:保存以下三个文件
- 第三步:运行!
- 源码展示
- main.go
- memdisk/memfs.go
- memdisk/webdav.go
- 注意事项
这个 Go 小玩具,让内存秒变网盘!关机就消失,重命名随便改,还能自动挂成 Z 盘——这不是魔法,是 Go 写的 WebDAV 内存文件系统!
你有没有想过:如果有一个网盘,不用注册、不占硬盘、速度飞快,但只要一关程序,所有数据就"人间蒸发"?听起来像间谍工具?其实它只是个用 Go 写的小玩具——而且现在,它还能让你在 Windows 资源管理器里直接重命名文件,就像操作本地文件一样丝滑!
今天,我们就来揭开这个"内存网盘"的神秘面纱。放心,代码不多,笑点管够,原理讲透,连你家猫都能看懂(大概)。
- 所有文件存在内存里(RAM),关进程就清零,干净得像没来过
- 支持完整的 WebDAV 协议:新建、删除、读写、建文件夹……
- 新增重磅功能:支持重命名文件/文件夹!拖拽、右键改名统统 OK
- 启动时自动在 Windows 上映射为 Z: 盘(可自定义),像本地磁盘一样用!
- 自带 README.txt 彩蛋,内含作者签名(不是病毒,真的!)
适用场景
- 临时共享
- 快速测试
- 演示环境
我们用 Go 的 map[string]*File 模拟文件目录结构,每个 File 可以是普通文件或目录。整棵树从根节点 / 开始,长得像这样:
/
└── README.txt
所有操作(创建、读取、删除、重命名)都是在这棵树上"修枝剪叶"。
普通 HTTP 只能 GET/POST,而 WebDAV 增加了 MKCOL(建目录)、PUT(上传)、MOVE(重命名)等方法。Go 的 golang.org/x/net/webdav 包帮我们实现了协议解析,我们只需提供底层文件系统的实现。
重命名的本质是:
- 从旧父目录中删掉条目
- 在新父目录中加上新名字
- 如果跨目录,还要更新父指针
关键是要同时锁住两个父目录,防止并发时"文件失踪"。我们的代码做到了这一点,安全又高效。
程序启动后,偷偷执行:
net use Z: http://localhost:8080 /y
于是你的资源管理器就多了一个"网络驱动器"。退出时再执行 /delete,不留痕迹。
确保已安装 Go(1.16+),并启用 Go Modules。
注意:三个文件需放在同一模块下,例如项目结构如下:
your-project/
├── go.mod
├── main.go
└── memdisk/
├── memfs.go
└── webdav.go
go run main.go -port=8080 -drive=Z:
然后打开"此电脑",看看是不是多了个 Z: 盘?双击进去,试试新建文件、重命名——是不是和本地磁盘一模一样?


package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"os/signal"
"sync"
"syscall"
"time"
"golang.org/x/net/webdav"
"memdisk/memdisk"
)
// 使用Windows net use命令映射网络驱动器
func mapNetworkDrive(serverURL, drive string) {
// 首先,如果已经连接则断开连接
exec.Command("net", "use", drive, "/delete", "/y").Run()
// 然后映射网络驱动器
cmd := exec.Command("net", "use", drive, serverURL)
if err := cmd.Run(); err != nil {
log.Printf("映射网络驱动器失败: %v", err)
// 尝试不同的方法
log.Printf("您可以手动映射驱动器: net use %s %s", drive, serverURL)
} else {
log.Printf("成功将 %s 映射到 %s", drive, serverURL)
}
}
// 使用Windows net use命令取消映射网络驱动器
func unmapNetworkDrive(drive string) {
log.Printf("正在取消映射网络驱动器: %s", drive)
cmd := exec.Command("net", "use", drive, "/delete", "/y")
if err := cmd.Run(); err != nil {
log.Printf("取消映射网络驱动器失败: %v", err)
} else {
log.Printf("成功取消映射 %s", drive)
}
}
func main() {
// 解析命令行参数
var port string
var drive string
flag.StringVar(&port, "port", "8080", "服务器端口")
flag.StringVar(&drive, "drive", "Z:", "映射的网络驱动器盘符")
flag.Parse()
// 如果使用旧的参数方式,仍然支持
if flag.NArg() > 0 {
port = flag.Arg(0)
}
if flag.NArg() > 1 {
drive = flag.Arg(1)
}
// 创建一个新的内存文件系统
fs := memdisk.NewMemFS()
// 只创建一个README文件
fs.WriteFile("/README.txt", []byte("内存文件系统rnrn这是一个运行在内存中的文件服务器。rn所有文件都存储在RAM中,服务器停止时将丢失。rnJjMgx"))
// 用于网络驱动器映射的服务器URL
serverURL := fmt.Sprintf("http://localhost:%s", port)
// 启动WebDAV服务器
startWebDAVServer(fs, port, drive, serverURL)
}
// 启动WebDAV服务器
func startWebDAVServer(fs *memdisk.MemFS, port, drive, serverURL string) {
// 创建WebDAV文件系统
wdFs := memdisk.NewWebDAVFS(fs)
// 创建WebDAV处理器
handler := &webdav.Handler{
FileSystem: wdFs,
LockSystem: webdav.NewMemLS(),
Logger: func(r *http.Request, err error) {
if err != nil {
log.Printf("WebDAV错误: %v %v", r.Method, err)
} else {
log.Printf("WebDAV请求: %v %v", r.Method, r.URL)
}
},
}
// 创建服务器多路复用器
mux := http.NewServeMux()
// 为所有路径注册WebDAV处理器
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 添加CORS头部
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, MKCOL, COPY, MOVE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Depth, Overwrite, Destination, Authorization")
// 添加可能影响显示名称的自定义头部
w.Header().Set("Server", "CustomWebDAVServer")
w.Header().Set("X-Server-Name", "MemoryFileSystem")
// 处理OPTIONS请求
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
// 调用WebDAV处理器
handler.ServeHTTP(w, r)
})
// 监听中断信号的通道
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
// 确保清理完成的等待组
var wg sync.WaitGroup
// 启动服务器goroutine并在准备好时映射网络驱动器
log.Printf("正在启动WebDAV服务器于 :%s", port)
log.Printf("访问服务器地址 http://localhost:%s", port)
log.Printf("尝试映射为网络驱动器: %s", drive)
// 服务器goroutine
server := &http.Server{
Addr: fmt.Sprintf(":%s", port),
Handler: mux,
}
go func() {
wg.Add(1)
defer wg.Done()
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}()
// 给服务器一点时间启动,然后映射网络驱动器
go func() {
time.Sleep(500 * time.Millisecond)
log.Printf("正在映射网络驱动器: %s 到 %s", drive, serverURL)
mapNetworkDrive(serverURL, drive)
}()
// 等待中断信号
<-sigChan
log.Println("正在关闭服务器...")
// 取消映射网络驱动器
unmapNetworkDrive(drive)
// 优雅地关闭服务器
if err := server.Shutdown(context.Background()); err != nil {
log.Printf("服务器关闭错误: %v", err)
}
// 等待所有goroutine完成
wg.Wait()
log.Println("服务器已优雅退出")
}
package memdisk
import (
"os"
"sync"
"time"
)
// File 表示内存文件系统中的一个文件
type File struct {
Filename string
Content []byte
FileSize int64
CreatedAt time.Time
ModifiedAt time.Time
IsDirectory bool
Children map[string]*File
Parent *File
mu sync.RWMutex
}
// MemFS 表示内存文件系统
type MemFS struct {
Root *File
mu sync.RWMutex
}
// NewMemFS 创建一个新的MemFS实例
func NewMemFS() *MemFS {
root := &File{
Filename: "",
IsDirectory: true,
Children: make(map[string]*File),
CreatedAt: time.Now(),
ModifiedAt: time.Now(),
}
return &MemFS{
Root: root,
}
}
// splitPath 将路径分割为其组成部分
func splitPath(path string) []string {
if path == "/" {
return []string{}
}
components := []string{}
current := ""
for _, c := range path {
if c == '/' {
if current != "" {
components = append(components, current)
current = ""
}
} else {
current += string(c)
}
}
if current != "" {
components = append(components, current)
}
return components
}
// GetFile 根据路径获取文件或目录
func (fs *MemFS) GetFile(path string) (*File, error) {
if path == "" {
return nil, os.ErrInvalid
}
fs.mu.RLock()
defer fs.mu.RUnlock()
components := splitPath(path)
current := fs.Root
for _, comp := range components {
current.mu.RLock()
child, exists := current.Children[comp]
current.mu.RUnlock()
if !exists {
return nil, os.ErrNotExist
}
current = child
}
return current, nil
}
// CreateDir 在给定路径中创建新目录
func (fs *MemFS) CreateDir(path string) (*File, error) {
if path == "" {
return nil, os.ErrInvalid
}
components := splitPath(path)
if len(components) == 0 {
return fs.Root, nil // 根目录已存在
}
// 在不持有全局锁的情况下获取父级
parentPathComponents := components[:len(components)-1]
parentPath := "/" + joinPath(parentPathComponents)
parent, err := fs.GetFile(parentPath)
if err != nil {
return nil, err
}
fs.mu.Lock()
defer fs.mu.Unlock()
parent.mu.Lock()
defer parent.mu.Unlock()
newDirName := components[len(components)-1]
if _, exists := parent.Children[newDirName]; exists {
return nil, os.ErrExist
}
newDir := &File{
Filename: newDirName,
IsDirectory: true,
Children: make(map[string]*File),
CreatedAt: time.Now(),
ModifiedAt: time.Now(),
Parent: parent,
}
parent.Children[newDirName] = newDir
return newDir, nil
}
// CreateFile 在给定路径中创建新文件
func (fs *MemFS) CreateFile(path string) (*File, error) {
if path == "" {
return nil, os.ErrInvalid
}
components := splitPath(path)
if len(components) == 0 {
return nil, os.ErrInvalid
}
// 在不持有全局锁的情况下获取父级
parentPathComponents := components[:len(components)-1]
parentPath := "/" + joinPath(parentPathComponents)
parent, err := fs.GetFile(parentPath)
if err != nil {
return nil, err
}
fs.mu.Lock()
defer fs.mu.Unlock()
parent.mu.Lock()
defer parent.mu.Unlock()
newFileName := components[len(components)-1]
if _, exists := parent.Children[newFileName]; exists {
return nil, os.ErrExist
}
newFile := &File{
Filename: newFileName,
IsDirectory: false,
Content: []byte{},
FileSize: 0,
CreatedAt: time.Now(),
ModifiedAt: time.Now(),
Parent: parent,
}
parent.Children[newFileName] = newFile
return newFile, nil
}
// ReadFile 读取文件内容
func (fs *MemFS) ReadFile(path string) ([]byte, error) {
file, err := fs.GetFile(path)
if err != nil {
return nil, err
}
file.mu.RLock()
defer file.mu.RUnlock()
if file.IsDirectory {
return nil, os.ErrInvalid
}
content := make([]byte, len(file.Content))
copy(content, file.Content)
return content, nil
}
// WriteFile 将内容写入文件
func (fs *MemFS) WriteFile(path string, content []byte) error {
file, err := fs.GetFile(path)
if err != nil {
// 如果文件不存在则尝试创建
file, err = fs.CreateFile(path)
if err != nil {
return err
}
}
file.mu.Lock()
defer file.mu.Unlock()
if file.IsDirectory {
return os.ErrInvalid
}
file.Content = make([]byte, len(content))
copy(file.Content, content)
file.FileSize = int64(len(content))
file.ModifiedAt = time.Now()
return nil
}
// Delete 删除文件或目录
func (fs *MemFS) Delete(path string) error {
if path == "/" {
return os.ErrPermission
}
components := splitPath(path)
if len(components) == 0 {
return os.ErrInvalid
}
// 在不持有全局锁的情况下获取父级
parentPathComponents := components[:len(components)-1]
parentPath := "/" + joinPath(parentPathComponents)
parent, err := fs.GetFile(parentPath)
if err != nil {
return err
}
fs.mu.Lock()
defer fs.mu.Unlock()
parent.mu.Lock()
defer parent.mu.Unlock()
fileName := components[len(components)-1]
if _, exists := parent.Children[fileName]; !exists {
return os.ErrNotExist
}
delete(parent.Children, fileName)
return nil
}
// Rename 重命名文件或目录
func (fs *MemFS) Rename(oldPath, newPath string) error {
if oldPath == "/" || newPath == "/" {
return os.ErrPermission
}
oldComponents := splitPath(oldPath)
newComponents := splitPath(newPath)
if len(oldComponents) == 0 || len(newComponents) == 0 {
return os.ErrInvalid
}
// 获取旧文件的父目录和文件名
oldParentPathComponents := oldComponents[:len(oldComponents)-1]
oldParentPath := "/" + joinPath(oldParentPathComponents)
oldParent, err := fs.GetFile(oldParentPath)
if err != nil {
return err
}
// 获取新文件的父目录和文件名
newParentPathComponents := newComponents[:len(newComponents)-1]
newParentPath := "/" + joinPath(newParentPathComponents)
newParent, err := fs.GetFile(newParentPath)
if err != nil {
return err
}
// 对相同对象只锁定一次
sameParent := oldParent == newParent
fs.mu.Lock()
defer fs.mu.Unlock()
oldParent.mu.Lock()
defer oldParent.mu.Unlock()
// 如果不是同一父目录,则锁定新父目录
if !sameParent {
newParent.mu.Lock()
defer newParent.mu.Unlock()
}
oldFileName := oldComponents[len(oldComponents)-1]
newFileName := newComponents[len(newComponents)-1]
// 检查旧文件是否存在
oldFile, exists := oldParent.Children[oldFileName]
if !exists {
return os.ErrNotExist
}
// 检查新文件是否已存在
if _, exists := newParent.Children[newFileName]; exists {
return os.ErrExist
}
// 从旧位置移除
delete(oldParent.Children, oldFileName)
// 更新文件名
oldFile.Filename = newFileName
// 更新父指针(如果不是同一父目录)
if !sameParent {
oldFile.Parent = newParent
}
// 添加到新位置
newParent.Children[newFileName] = oldFile
// 更新修改时间
oldFile.ModifiedAt = time.Now()
return nil
}
// ListDir 列出目录内容
func (fs *MemFS) ListDir(path string) ([]*File, error) {
dir, err := fs.GetFile(path)
if err != nil {
return nil, err
}
dir.mu.RLock()
defer dir.mu.RUnlock()
if !dir.IsDirectory {
return nil, os.ErrInvalid
}
children := make([]*File, 0, len(dir.Children))
for _, child := range dir.Children {
child.mu.RLock()
// 创建副本以避免竞态条件
copyChild := &File{
Filename: child.Filename,
FileSize: child.FileSize,
CreatedAt: child.CreatedAt,
ModifiedAt: child.ModifiedAt,
IsDirectory: child.IsDirectory,
}
child.mu.RUnlock()
children = append(children, copyChild)
}
return children, nil
}
// joinPath 将路径组件连接成单个路径字符串
func joinPath(components []string) string {
result := ""
for i, comp := range components {
if i > 0 {
result += "/"
}
result += comp
}
return result
}
package memdisk
import (
"context"
"io"
"os"
"time"
"golang.org/x/net/webdav"
)
// 为File实现os.FileInfo接口
func (f *File) Name() string {
return f.Filename
}
func (f *File) Size() int64 {
return f.FileSize
}
func (f *File) Mode() os.FileMode {
if f.IsDirectory {
return 0755 | os.ModeDir
}
return 0644
}
func (f *File) ModTime() time.Time {
return f.ModifiedAt
}
func (f *File) IsDir() bool {
return f.IsDirectory
}
func (f *File) Sys() interface{} {
return nil
}
// ... 移除兼容性层,现在直接实现Name()和Size() ...
// WebDAVFS实现了webdav.FileSystem接口
type WebDAVFS struct {
fs *MemFS
}
// NewWebDAVFS创建一个新的WebDAVFS实例
func NewWebDAVFS(fs *MemFS) *WebDAVFS {
return &WebDAVFS{
fs: fs,
}
}
// Mkdir创建一个新目录
func (wfs *WebDAVFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
_, err := wfs.fs.CreateDir(name)
return err
}
// OpenFile打开一个文件
func (wfs *WebDAVFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (webdav.File, error) {
// 检查文件是否存在
file, err := wfs.fs.GetFile(name)
if err != nil {
if os.IsNotExist(err) && (flag&os.O_CREATE != 0) {
// 如果文件不存在且指定了O_CREATE标志,则创建文件
file, err = wfs.fs.CreateFile(name)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
// 返回WebDAVFile包装器
return &WebDAVFile{
file: file,
pos: 0,
}, nil
}
// RemoveAll删除文件或目录
func (wfs *WebDAVFS) RemoveAll(ctx context.Context, name string) error {
return wfs.fs.Delete(name)
}
// Rename重命名文件或目录
func (wfs *WebDAVFS) Rename(ctx context.Context, oldName, newName string) error {
return wfs.fs.Rename(oldName, newName)
}
// Stat返回文件信息
func (wfs *WebDAVFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
file, err := wfs.fs.GetFile(name)
if err != nil {
return nil, err
}
return file, nil
}
// WebDAVFile实现了webdav.File接口
type WebDAVFile struct {
file *File
pos int64
}
// Close关闭文件
func (wf *WebDAVFile) Close() error {
return nil // 内存文件无需操作
}
// Read从文件读取
func (wf *WebDAVFile) Read(p []byte) (n int, err error) {
wf.file.mu.RLock()
defer wf.file.mu.RUnlock()
if wf.pos >= int64(len(wf.file.Content)) {
return 0, io.EOF
}
n = copy(p, wf.file.Content[wf.pos:])
wf.pos += int64(n)
return n, nil
}
// ReadAt在指定偏移位置从文件读取
func (wf *WebDAVFile) ReadAt(p []byte, off int64) (n int, err error) {
wf.file.mu.RLock()
defer wf.file.mu.RUnlock()
if off >= int64(len(wf.file.Content)) {
return 0, io.EOF
}
n = copy(p, wf.file.Content[off:])
if n < len(p) {
err = io.EOF
}
return n, err
}
// Seek定位到指定位置
func (wf *WebDAVFile) Seek(offset int64, whence int) (int64, error) {
wf.file.mu.RLock()
defer wf.file.mu.RUnlock()
var newPos int64
switch whence {
case io.SeekStart:
newPos = offset
case io.SeekCurrent:
newPos = wf.pos + offset
case io.SeekEnd:
newPos = int64(len(wf.file.Content)) + offset
default:
return 0, os.ErrInvalid
}
if newPos < 0 {
return 0, os.ErrInvalid
}
wf.pos = newPos
return newPos, nil
}
// Write向文件写入
func (wf *WebDAVFile) Write(p []byte) (n int, err error) {
wf.file.mu.Lock()
defer wf.file.mu.Unlock()
// 计算写入后的新位置
newPos := wf.pos + int64(len(p))
// 如需要则调整内容大小
if newPos > int64(len(wf.file.Content)) {
// 扩展内容
newContent := make([]byte, newPos)
copy(newContent, wf.file.Content)
wf.file.Content = newContent
}
// 将p写入当前位置
copy(wf.file.Content[wf.pos:newPos], p)
// 更新位置和大小
wf.pos = newPos
wf.file.FileSize = newPos
wf.file.ModifiedAt = time.Now()
return len(p), nil
}
// WriteAt在指定偏移位置向文件写入
func (wf *WebDAVFile) WriteAt(p []byte, off int64) (n int, err error) {
wf.file.mu.Lock()
defer wf.file.mu.Unlock()
newPos := off + int64(len(p))
if newPos > int64(len(wf.file.Content)) {
// 扩展内容
newContent := make([]byte, newPos)
copy(newContent, wf.file.Content)
wf.file.Content = newContent
}
copy(wf.file.Content[off:newPos], p)
// 如需要则更新大小
if newPos > wf.file.FileSize {
wf.file.FileSize = newPos
}
wf.file.ModifiedAt = time.Now()
return len(p), nil
}
// Readdir读取目录条目
func (wf *WebDAVFile) Readdir(count int) ([]os.FileInfo, error) {
wf.file.mu.RLock()
defer wf.file.mu.RUnlock()
if !wf.file.IsDirectory {
return nil, os.ErrInvalid
}
// 将map值转换为切片
children := make([]os.FileInfo, 0, len(wf.file.Children))
for _, child := range wf.file.Children {
children = append(children, child)
}
// 处理count参数
if count <= 0 {
return children, nil
}
if count > len(children) {
return children, io.EOF
}
return children[:count], nil
}
// Readdirnames读取目录条目名称
func (wf *WebDAVFile) Readdirnames(n int) ([]string, error) {
wf.file.mu.RLock()
defer wf.file.mu.RUnlock()
if !wf.file.IsDirectory {
return nil, os.ErrInvalid
}
// 将map键转换为切片
names := make([]string, 0, len(wf.file.Children))
for name := range wf.file.Children {
names = append(names, name)
}
// 处理n参数
if n <= 0 {
return names, nil
}
if n > len(names) {
return names, io.EOF
}
return names[:n], nil
}
// Stat返回文件信息
func (wf *WebDAVFile) Stat() (os.FileInfo, error) {
return wf.file, nil
}
- 此项目仅限本地测试,无任何身份验证,请勿暴露到公网!
- Windows 映射功能仅在 Windows 上有效(废话文学+1)
- 数据随进程消亡——所以别把情书存进去,除非你想制造"数字失忆"浪漫
注意:README.txt 末尾的 JjMgx 是作者签名,不是乱码,也不是病毒哈 🙂
到此这篇关于使用Go语言实现的WebDAV内存文件系统的文章就介绍到这了,更多相关Go语言内存文件系统内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!
您可能感兴趣的文章:
- 基于Go语言实现高性能文件上传下载系统
- Golang使用minio替代文件系统的实战教程
- 详解Golang中文件系统事件监听
- go实现一个内存缓存系统的示例代码