Repository: AlistGo/alist (language: go)

Issue:
sftp存储 bug
### Please make sure of the following things

- [x] I have read the [documentation](https://alist.nn.ci).
我已经阅读了[文档](https://alist.nn.ci)。

- [x] I'm sure there are no duplicate issues or discussions.
我确定没有重复的issue或讨论。

- [x] I'm sure it's due to `AList` and not something else(such as [Network](https://alist.nn.ci/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host) ,`Dependencies` or `Operational`).
我确定是`AList`的问题，而不是其他原因（例如[网络](https://alist.nn.ci/zh/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host)，`依赖`或`操作`）。

- [x] I'm sure this issue is not fixed in the latest version.
我确定这个问题在最新版本中没有被修复。


### AList Version / AList 版本

v3.33.0

### Driver used / 使用的存储驱动

SFTP

### Describe the bug / 问题描述

经常接上不到半天就坏了，报错： failed get objs: failed to list objs: connection lost
必须重启（restart docker）才能再次正常连接
之前有人提过（[issue-6042](https://github.com/alist-org/alist/issues/6042)），评论中不少人也遇到了，但没有被开发者注意到。能不能尽快修正下这个 bug？

### Reproduction / 复现链接

内网未开放公网

### Config / 配置

内网未开放公网

### Logs / 日志

链接页面显示： failed get objs: failed to list objs: connection lost
docker 日志似乎无报错显示


Discussion / hints:
Thanks for opening your first issue here! Be sure to follow the issue template!

同样遇到此问题，缓存设置为1分钟也不会自动重新连接，请早日修复，谢谢！

Fix patch:
diff --git a/drivers/sftp/driver.go b/drivers/sftp/driver.go
index 77f5198457c..1f216598d2d 100644
--- a/drivers/sftp/driver.go
+++ b/drivers/sftp/driver.go
@@ -16,7 +16,8 @@ import (
 type SFTP struct {
 	model.Storage
 	Addition
-	client *sftp.Client
+	client                *sftp.Client
+	clientConnectionError error
 }
 
 func (d *SFTP) Config() driver.Config {
@@ -39,6 +40,9 @@ func (d *SFTP) Drop(ctx context.Context) error {
 }
 
 func (d *SFTP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
+	if err := d.clientReconnectOnConnectionError(); err != nil {
+		return nil, err
+	}
 	log.Debugf("[sftp] list dir: %s", dir.GetPath())
 	files, err := d.client.ReadDir(dir.GetPath())
 	if err != nil {
@@ -51,6 +55,9 @@ func (d *SFTP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]
 }
 
 func (d *SFTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
+	if err := d.clientReconnectOnConnectionError(); err != nil {
+		return nil, err
+	}
 	remoteFile, err := d.client.Open(file.GetPath())
 	if err != nil {
 		return nil, err
@@ -62,14 +69,23 @@ func (d *SFTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
 }
 
 func (d *SFTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
+	if err := d.clientReconnectOnConnectionError(); err != nil {
+		return err
+	}
 	return d.client.MkdirAll(path.Join(parentDir.GetPath(), dirName))
 }
 
 func (d *SFTP) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
+	if err := d.clientReconnectOnConnectionError(); err != nil {
+		return err
+	}
 	return d.client.Rename(srcObj.GetPath(), path.Join(dstDir.GetPath(), srcObj.GetName()))
 }
 
 func (d *SFTP) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
+	if err := d.clientReconnectOnConnectionError(); err != nil {
+		return err
+	}
 	return d.client.Rename(srcObj.GetPath(), path.Join(path.Dir(srcObj.GetPath()), newName))
 }
 
@@ -78,10 +94,16 @@ func (d *SFTP) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
 }
 
 func (d *SFTP) Remove(ctx context.Context, obj model.Obj) error {
+	if err := d.clientReconnectOnConnectionError(); err != nil {
+		return err
+	}
 	return d.remove(obj.GetPath())
 }
 
 func (d *SFTP) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
+	if err := d.clientReconnectOnConnectionError(); err != nil {
+		return err
+	}
 	dstFile, err := d.client.Create(path.Join(dstDir.GetPath(), stream.GetName()))
 	if err != nil {
 		return err
diff --git a/drivers/sftp/util.go b/drivers/sftp/util.go
index 3deb8dcf94b..eaeeaff5814 100644
--- a/drivers/sftp/util.go
+++ b/drivers/sftp/util.go
@@ -4,6 +4,7 @@ import (
 	"path"
 
 	"github.com/pkg/sftp"
+	log "github.com/sirupsen/logrus"
 	"golang.org/x/crypto/ssh"
 )
 
@@ -30,6 +31,23 @@ func (d *SFTP) initClient() error {
 		return err
 	}
 	d.client, err = sftp.NewClient(conn)
+	if err == nil {
+		d.clientConnectionError = nil
+		go func(d *SFTP) {
+			d.clientConnectionError = d.client.Wait()
+		}(d)
+	}
+	return err
+}
+
+func (d *SFTP) clientReconnectOnConnectionError() error {
+	err := d.clientConnectionError
+	if err == nil {
+		return nil
+	}
+	log.Debugf("[sftp] discarding closed sftp connection: %v", err)
+	_ = d.client.Close()
+	err = d.initClient()
 	return err
 }
 


Test patch: