在前面章节已经展示了一些关于SMB的基本介绍,以及对应SMB相关操作的Java实现,这一章主要是前一章的补充,使用Golang来对 SMB共享文件夹进行操作。如果没有阅读过上一章节的同学,请跳转到 基于Smb协议实现网络文件传输,本文不再重复介绍。
版本介绍
本文使用到的主要框架版本如下
- Golang 1.20.5
- github.com/gin-gonic/gin v1.9.1
- github.com/hirochachacha/go-smb2 v1.1.0
Smb操作实现
smb2 包在 [MS-SMB2] 中实现了 SMB2/3 客户端
API文档:https://pkg.go.dev/github.com/hirochachacha/go-smb2
SMBClient.go 模块
导入依赖包
import (
"github.com/hirochachacha/go-smb2"
"io"
"net"
"os"
"regexp"
)
如果不能正常导入依赖包,可能会用到以下指令下载依赖包或者清理不必要依赖包 :
go get -u
go mod tidy
创建SMBClient 类
首先我们定义和初始化SMBClient
类用到的变量
type SMBClient struct {
conn net.Conn
dialer *smb2.Dialer
session *smb2.Session
share *smb2.Share
}
func NewSMBClient(server, username, password, sharename string) (*SMBClient, error) {
conn, err := net.Dial("tcp", server+":445")
if err != nil {
return nil, err
}
d := &smb2.Dialer{
Initiator: &smb2.NTLMInitiator{
User: username,
Password: password,
},
}
s, err := d.Dial(conn)
if err != nil {
return nil, err
}
share, err := s.Mount(sharename)
if err != nil {
return nil, err
}
return &SMBClient{
conn: conn,
dialer: d,
session: s,
share: share,
}, nil
}
封装 SMBClient类的常用方法
把一些常用的增删改查操作封装在SMBClient
func (c *SMBClient) Close() {
c.share.Umount()
c.session.Logoff()
c.conn.Close()
}
func (c *SMBClient) Upload(localPath, remotePath string) error {
srcFile, err := os.Open(localPath)
if err != nil {
return err
}
defer srcFile.Close()
dstFile, err := c.share.Create(remotePath)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
}
return nil
}
func (c *SMBClient) getMatchingFiles(basePath, pattern string) ([]string, error) {
var results []string
regex := regexp.MustCompile(pattern)
fis, err := c.share.ReadDir(basePath)
if err != nil {
return nil, err
}
for _, fi := range fis {
if !fi.IsDir() && regex.MatchString(fi.Name()) {
results = append(results, basePath+"/"+fi.Name())
}
}
return results, nil
}
func (c *SMBClient) Download(basePath, remotePattern, localPath string) error {
matchingFiles, err := c.getMatchingFiles(basePath, remotePattern)
if err != nil {
return err
}
for _, remoteFIle := range matchingFiles {
srcFile, err := c.share.Open(remoteFIle)
if err != nil {
return err
}
defer srcFile.Close()
stat, err := srcFile.Stat()
if err != nil {
return err
}
dstFile, err := os.Create(localPath + stat.Name())
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
if err != nil {
return err
}
}
return nil
}
func (c *SMBClient) Delete(basePath, remotePattern string) error {
matchingFiles, err := c.getMatchingFiles(basePath, remotePattern)
if err != nil {
return err
}
for _, remotePath := range matchingFiles {
err := c.share.Remove(remotePath)
if err != nil {
return err
}
}
return nil
}
func (c *SMBClient) Rename(basePath, oldPattern, suffix string) error {
matchingFiles, err := c.getMatchingFiles(basePath, oldPattern)
if err != nil {
return err
}
for _, oldPath := range matchingFiles {
err := c.share.Rename(oldPath, oldPath+suffix)
if err != nil {
return err
}
}
return nil
}
func (c *SMBClient) Search(basePath, pattern string) ([]string, error) {
return c.getMatchingFiles(basePath, pattern)
}
SMBMain.go 模块
该模块是程序入口,这里使用到了Gin
作为Web框架,方便大家通过 API调用SMB相关操作进行测试。
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
client, err := NewSMBClient("192.168.50.69", "user", "123456", "LANdrive")
if err != nil {
panic(err)
}
defer client.Close()
router := gin.Default()
router.POST("/upload", func(c *gin.Context) {
localPath := c.PostForm("localPath")
remotePath := c.PostForm("remotePath")
fmt.Printf("localPath: %s, remotePath: %s\n", localPath, remotePath)
err := client.Upload(localPath, remotePath)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"message": "Upload successful"})
})
router.GET("/download", func(c *gin.Context) {
basePath := c.Query("basePath")
remotePattern := c.Query("remotePattern")
localPath := c.Query("localPath")
err := client.Download(basePath, remotePattern, localPath)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"message": "Download successful"})
})
router.PUT("/rename", func(c *gin.Context) {
basePath := c.Query("basePath")
oldPattern := c.Query("oldPattern")
suffix := c.Query("suffix")
err := client.Rename(basePath, oldPattern, suffix)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"message": "Rename successful"})
})
router.GET("/search", func(c *gin.Context) {
basePath := c.Query("basePath")
pattern := c.Query("pattern")
results, err := client.Search(basePath, pattern)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"files": results})
})
router.DELETE("/delete", func(c *gin.Context) {
basePath := c.Query("basePath")
remotePattern := c.Query("remotePattern")
err := client.Delete(basePath, remotePattern)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"message": "Delete successful"})
})
router.Run(":8080")
}
通过Postman进行调用
大家可以在本文后面的Github链接下载完整代码,运行SMBMain.go
模块即可启动Web服务。
文件上传
这里我把本地的downloaded_file.txt
文件上传到远程共享文件夹test
目录,远程文件夹文件名为downloaded_file.ext
文件成功上传到共享文件夹
文件下载
这里把远程文件夹test
下的所有文件下载到本地/Users/evan/Downloads/
目录,remotePattern
是匹配远程目录下文件的正则表达式
文件下载成功
文件查询
这里查询所有在远程test
目录下符合.*
正则表达式的文件
文件改名
这里把远程目录test
下所有符合.*$
的文件加上.d2
后缀
远程文件改名成功
文件删除
这里把远程目录test
下符合.*$
正则表达式的文件删除
远程目录下的文件被删除成功
完整代码
链接: https://github.com/EvanLeung08/eshare-smb-client-in-golang
上面示例的Postman Collection
已经到处到以下文件,直接把Golang SMB.postman_collection.json
导入到你的Postman即可