内存网盘 - Go语言实现的WebDAV内存文件系统

这个 Go 小玩具,让内存秒变网盘!关机就消失,重命名随便改,还能自动挂成 Z 盘------这不是魔法,是 Go 写的 WebDAV 内存文件系统!

你有没有想过:如果有一个网盘,不用注册、不占硬盘、速度飞快,但只要一关程序,所有数据就"人间蒸发"?听起来像间谍工具?其实它只是个用 Go 写的小玩具------而且现在,它还能让你在 Windows 资源管理器里直接重命名文件,就像操作本地文件一样丝滑!

今天,我们就来揭开这个"内存网盘"的神秘面纱。放心,代码不多,笑点管够,原理讲透,连你家猫都能看懂(大概)。

🎯 功能特性

  • ✅ 所有文件存在内存里(RAM),关进程就清零,干净得像没来过
  • ✅ 支持完整的 WebDAV 协议:新建、删除、读写、建文件夹......
  • ✅ 新增重磅功能:支持重命名文件/文件夹!拖拽、右键改名统统 OK
  • ✅ 启动时自动在 Windows 上映射为 Z: 盘(可自定义),像本地磁盘一样用!
  • ✅ 自带 README.txt 彩蛋,内含作者签名(不是病毒,真的!)

适用场景

  • 临时共享
  • 快速测试
  • 演示环境

🛠️ 技术原理

1. 内存文件系统 = 一棵树

我们用 Go 的 map[string]*File 模拟文件目录结构,每个 File 可以是普通文件或目录。整棵树从根节点 / 开始,长得像这样:

复制代码
/
└── README.txt

所有操作(创建、读取、删除、重命名)都是在这棵树上"修枝剪叶"。

2. WebDAV = HTTP 的"文件操作扩展包"

普通 HTTP 只能 GET/POST,而 WebDAV 增加了 MKCOL(建目录)、PUT(上传)、MOVE(重命名)等方法。Go 的 golang.org/x/net/webdav 包帮我们实现了协议解析,我们只需提供底层文件系统的实现。

3. 重命名 = "搬家+改名"

重命名的本质是:

  1. 从旧父目录中删掉条目
  2. 在新父目录中加上新名字
  3. 如果跨目录,还要更新父指针

关键是要同时锁住两个父目录,防止并发时"文件失踪"。我们的代码做到了这一点,安全又高效。

4. Windows 映射 = net use 命令自动化

程序启动后,偷偷执行:

cmd 复制代码
net use Z: http://localhost:8080 /y

于是你的资源管理器就多了一个"网络驱动器"。退出时再执行 /delete,不留痕迹。

📦 使用方法

第一步:准备环境

确保已安装 Go(1.16+),并启用 Go Modules。

第二步:保存以下三个文件

注意:三个文件需放在同一模块下,例如项目结构如下:

go 复制代码
your-project/
├── go.mod
├── main.go
└── memdisk/
    ├── memfs.go
    └── webdav.go

第三步:运行!

bash 复制代码
go run main.go -port=8080 -drive=Z:

然后打开"此电脑",看看是不是多了个 Z: 盘?双击进去,试试新建文件、重命名------是不是和本地磁盘一模一样?

📄 源码展示

main.go

go 复制代码
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("内存文件系统\r\n\r\n这是一个运行在内存中的文件服务器。\r\n所有文件都存储在RAM中,服务器停止时将丢失。\r\nJjMgx"))

	// 用于网络驱动器映射的服务器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("服务器已优雅退出")
}

memdisk/memfs.go

go 复制代码
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
}

memdisk/webdav.go

go 复制代码
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 是作者签名,不是乱码,也不是病毒哈 :)

往期部分文章列表

相关推荐
梦想很大很大12 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
lekami_兰17 小时前
MySQL 长事务:藏在业务里的性能 “隐形杀手”
数据库·mysql·go·长事务
却尘21 小时前
一篇小白也能看懂的 Go 字符串拼接 & Builder & cap 全家桶
后端·go
ん贤21 小时前
一次批量删除引发的死锁,最终我选择不加锁
数据库·安全·go·死锁
mtngt111 天前
AI DDD重构实践
go
Grassto3 天前
12 go.sum 是如何保证依赖安全的?校验机制源码解析
安全·golang·go·哈希算法·go module
Grassto5 天前
11 Go Module 缓存机制详解
开发语言·缓存·golang·go·go module
程序设计实验室6 天前
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
go
我的golang之路果然有问题6 天前
使用 Hugo + GitHub Pages + PaperMod 主题 + Obsidian 搭建开发博客
golang·go·github·博客·个人开发·个人博客·hugo
啊汉7 天前
古文观芷App搜索方案深度解析:打造极致性能的古文搜索引擎
go·软件随想