From 15f6380b127fc27f7885b47d873608f81d4c4a42 Mon Sep 17 00:00:00 2001 From: okatu-loli Date: Thu, 4 Jun 2026 16:47:50 +0800 Subject: [PATCH] fix(guangyapan): rate-limit API requests to avoid flooding on batch copy Every GuangYaPan API call funnels through postAPI with no throttling, so copying many files cross-storage fired unthrottled Link/copy/list requests and overwhelmed the upstream API. Other Chinese pan drivers (123, aliyundrive_open) self-throttle; guangya did not. Add a per-endpoint rate.Limiter (one request per 500ms, applied in postAPI), mirroring the 123 driver's APIRateLimit pattern, so concurrent copy tasks are paced instead of flooding. --- drivers/guangyapan/driver.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/guangyapan/driver.go b/drivers/guangyapan/driver.go index e18d1b795ca..e3159a2edc7 100644 --- a/drivers/guangyapan/driver.go +++ b/drivers/guangyapan/driver.go @@ -9,6 +9,7 @@ import ( "io" "net/url" "strings" + "sync" "time" "github.com/alist-org/alist/v3/drivers/base" @@ -19,6 +20,7 @@ import ( "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/go-resty/resty/v2" log "github.com/sirupsen/logrus" + "golang.org/x/time/rate" ) const ( @@ -36,8 +38,15 @@ type GuangYaPan struct { resolvedRootFolderID string rootFolderResolved bool + + // apiRateLimit throttles requests per API endpoint so that batch operations + // (e.g. copying many files cross-storage) don't flood the upstream API. + apiRateLimit sync.Map } +// apiRateInterval is the minimum gap between two requests to the same endpoint. +const apiRateInterval = 500 * time.Millisecond + func (d *GuangYaPan) Config() driver.Config { return config } @@ -793,10 +802,18 @@ func (d *GuangYaPan) accountErr(desc, short string, resp *resty.Response) string return msg } +func (d *GuangYaPan) apiRateLimitWait(ctx context.Context, path string) error { + value, _ := d.apiRateLimit.LoadOrStore(path, rate.NewLimiter(rate.Every(apiRateInterval), 1)) + return value.(*rate.Limiter).Wait(ctx) +} + func (d *GuangYaPan) postAPI(ctx context.Context, path string, body any, out any) error { if strings.TrimSpace(d.AccessToken) == "" { return errors.New("access token is empty") } + if err := d.apiRateLimitWait(ctx, path); err != nil { + return err + } resp, err := d.apiClient.R(). SetContext(ctx). SetHeader("Authorization", "Bearer "+d.AccessToken).