golang使用sftp上传和下载文件例子

Published on:
Tags: golang sftp ssh

最近在做一个功能给客户端发包。

客户端上传包到后台,选择发布的环境后,进行发布,实现版本热更。

连接的参数#

发包功能关键的一部分是发布到不同的环境服务器, OpsEnv 结构体定义了环境服务器的信息,
其中 Addr,Port,Passport,Password,PubKey,AuthType 六个字段和发包相关。

model.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package sftp

// OpsEnv 环境
type OpsEnv struct {
Id int `orm:"id,primary" json:"id"` // id
EnvName string `orm:"env_name" json:"envName"` // 环境名称
EnvType int `orm:"env_type" json:"envType"` // 环境类型
Addr string `orm:"addr" json:"addr"` // 发送地址
Port string `orm:"port" json:"port"` // 登录端口
Passport string `orm:"passport" json:"passport"` //登录名
Password string `orm:"password" json:"password"` //登录密码
PubKey string `orm:"pub_key" json:"pubKey"` //私钥路径
AuthType int `orm:"auth_type" json:"authType"` //认证类型 1公钥 2密码
Path string `orm:"path" json:"path"` // 发送路径
Domain string `orm:"domain" json:"domain"` // 访问域名
}

获得连接#

SFTP是用SSH封装过的FTP协议,相当于经过加密的FTP协议,功能与FTP一样,只是传输数据经过加密。

所以首先需要获得ssh连接。

newSshClient 方法提供了两种连接方式——密码和密钥。

getSftpClient 方法在获得ssh连接的基础上,获得sftp连接。

ssh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package sftp

import (
"fmt"
"github.com/mitchellh/go-homedir"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
"log"
"os"
"time"
)

// getSshClient 获取ssh连接
func getSshClient() (*ssh.Client, error) {
env := &OpsEnv{
Passport: "zhagnSan",
Password: "123456",
//PubKey: "/root/.ssh/id_rsa",
Addr: "192.168.99.106",
Port: "22",
AuthType: 2,
}
conn, err := newSshClient(env)
if err != nil {
return nil, err
}
return conn, nil
}

// 最大32m
const maxPacket = 1 << 15

// getSftpClient 获取sftp连接
func getSftpClient() (*sftp.Client, error) {
conn, err := getSshClient()
if err != nil {
return nil, err
}
return sftp.NewClient(conn, sftp.MaxPacket(maxPacket))
}

// newSshClient 连接ssh
func newSshClient(h *OpsEnv) (*ssh.Client, error) {
config := &ssh.ClientConfig{
Timeout: time.Second * 5,
User: h.Passport,
HostKeyCallback: ssh.InsecureIgnoreHostKey(), //这个可以, 但是不够安全
}
//认证方式包括密码和私钥文件两种
if h.AuthType == 2 {
config.Auth = []ssh.AuthMethod{ssh.Password(h.Password)}
} else {
config.Auth = []ssh.AuthMethod{publicKeyAuthFunc(h.PubKey)}
}
addr := fmt.Sprintf("%s:%s", h.Addr, h.Port)
c, err := ssh.Dial("tcp", addr, config)
if err != nil {
return nil, err
}
return c, nil
}

// publicKeyAuthFunc 读取&&解析私钥
func publicKeyAuthFunc(kPath string) ssh.AuthMethod {
keyPath, err := homedir.Expand(kPath)
if err != nil {
log.Fatal("find key's home dir failed", err)
}
key, err := os.ReadFile(keyPath)
if err != nil {
log.Fatal("ssh key file read failed", err)
}
// CreateUserOfRole the Signer for this private key.
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatal("ssh key signer failed", err)
}
return ssh.PublicKeys(signer)
}

取得sftp连接后,接下来就能使用该sftp连接执行文件上传和下载的操作。

上传文件#

Upload 方法提供上传文件功能,上传文件分几步实现:

  • 打开本地文件,读取内容
  • 创建远程目录
  • 创建或者清空远程文件
  • 修改远程文件的权限
  • 复制本地文件内容到远程文件

下载文件#

下载文件与上传文件的操作类似,唯一的区别是把远程文件的内容写到本地文件。

sftp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package sftp

import (
"fmt"
"io"
"os"
"path/filepath"
)

// Upload 上传文件
func Upload(localPath, remotePath string) error {
client, err := getSftpClient()
defer client.Close()
if err != nil {
return err
}
localFile, err := os.Open(localPath)
if err != nil {
return fmt.Errorf("BrowserOpen local file failed %s", err)
}
defer localFile.Close()
//文件路径
remoteFilePath := filepath.ToSlash(filepath.Dir(remotePath))
//检查目录,必要时创建目录
err = client.MkdirAll(remoteFilePath)
if err != nil {
return fmt.Errorf("scp mkdir all failed %s", err)
}
//创建或着清空文件
remoteFile, err := client.Create(remotePath)
if err != nil {
return fmt.Errorf("create remote file failed %s:%s", remotePath, err)
}
defer remoteFile.Close()
//修改执行权限
info, err := os.Lstat(localPath)
if err != nil {
return err
}
err = client.Chmod(remoteFile.Name(), info.Mode())
if err != nil {
return fmt.Errorf("scp chmod failed %s", err)
}
//写入内容,存在则覆盖
_, err = io.Copy(remoteFile, localFile)
if err != nil {
return fmt.Errorf("io copy failed %s", err)
}
return nil
}

// Download 下载文件
func Download(localPath, remotePath string) error {
client, err := getSftpClient()
defer client.Close()
if err != nil {
return err
}
remoteFile, err := client.Open(remotePath)
if err != nil {
return fmt.Errorf("open remote file failed %s", err)
}
defer remoteFile.Close()

localFile, err := os.Create(localPath)
if err != nil {
return fmt.Errorf("create local file failed %s", err)
}
defer localFile.Close()

//写入内容,存在则覆盖
_, err = io.Copy(localFile, remoteFile)
if err != nil {
return fmt.Errorf("io copy failed %s", err)
}
return nil
}

远程执行命令#

文件经过打包压缩,才上传到远程服务器上,因此还需要对远程文件进行解压缩。

Unzip 方法接收一个解压缩的命令参数,然后调用 runCommand 方法远程执行命令,最后返回命令的执行结果。

unzip.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package sftp

import (
"bytes"
"fmt"
"golang.org/x/crypto/ssh"
)

// Unzip unzip解压缩
func Unzip(command string) error {
conn, err := getSshClient()
if err != nil {
return err
}
defer conn.Close()
res, err := runCommand(conn, command)
if err != nil {
return err
}
fmt.Println("res", res)
return nil
}

// runCommand 远程执行命令
func runCommand(client *ssh.Client, command string) (stdout string, err error) {
session, err := client.NewSession()
if err != nil {
return
}
defer session.Close()
var buf bytes.Buffer
session.Stdout = &buf
session.Stderr = &buf
err = session.Run(command)
if err != nil {
err = fmt.Errorf("%s", string(buf.Bytes()))
return
}
stdout = string(buf.Bytes())
return
}

调用&&测试#

main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"gfast/example/ssh/sftp"
"github.com/gogf/gf/frame/g"
)

var (
localPath = "./test/hall.zip"
remotePath = "/home/zhagnSan/oooo/hall/hall.zip"
)

func main() {
Upload()
//Download()
//Unzip()
}

func Upload() {
err := sftp.Upload(localPath, remotePath)
if err != nil {
g.Log().Info(err.Error())
}
}

func Download() {
err := sftp.Download(localPath, remotePath)
if err != nil {
g.Log().Info(err.Error())
}
}

func Unzip() {
command := fmt.Sprintf("%s %s %s", "unzip -o", remotePath, "-d /home/zhagnSan/oooo/hall")
err := sftp.Unzip(command)
if err != nil {
g.Log().Info(err.Error())
}
}