mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-04 20:46:37 +08:00
优化,添加115接口文档
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -472,3 +473,65 @@ func TestFsListRefreshContinue(t *testing.T) {
|
||||
t.Fatalf("want 1 file, got %d", len(files))
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetDownloadURLsBatch 验证批量换链:多个 pick_code 合并为一次逗号分隔
|
||||
// 请求;响应按文件 ID 为键、以条目内 pick_code 映射回请求侧;已缓存的
|
||||
// pick_code 不再发起请求;缺失项(空 URL)不出现在结果中。
|
||||
func TestGetDownloadURLsBatch(t *testing.T) {
|
||||
var mu sync.Mutex
|
||||
var requests []string
|
||||
mockAPI(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/open/ufile/downurl" {
|
||||
t.Errorf("unexpected path %s", r.URL.Path)
|
||||
}
|
||||
pc := r.PostFormValue("pick_code")
|
||||
mu.Lock()
|
||||
requests = append(requests, pc)
|
||||
mu.Unlock()
|
||||
w.Write([]byte(`{"state":true,"data":{
|
||||
"111":{"pick_code":"batch-pc-a","url":{"url":"https://cdn/a.mkv"}},
|
||||
"222":{"pick_code":"batch-pc-b","url":{"url":"https://cdn/b.jpg"}},
|
||||
"333":{"pick_code":"batch-pc-empty","url":{"url":""}}}}`))
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
ClearDownloadURLCache("batch-pc-a")
|
||||
ClearDownloadURLCache("batch-pc-b")
|
||||
ClearDownloadURLCache("batch-pc-empty")
|
||||
})
|
||||
|
||||
c := NewOpenClient("100195125", "at1", "rt1")
|
||||
urls, err := c.GetDownloadURLsBatch(context.Background(),
|
||||
[]string{"batch-pc-a", "batch-pc-b", "batch-pc-empty", "", "batch-pc-a"}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("batch downurl: %v", err)
|
||||
}
|
||||
if urls["batch-pc-a"] != "https://cdn/a.mkv" || urls["batch-pc-b"] != "https://cdn/b.jpg" {
|
||||
t.Fatalf("bad urls: %#v", urls)
|
||||
}
|
||||
if _, ok := urls["batch-pc-empty"]; ok {
|
||||
t.Fatalf("empty-url entry should be absent: %#v", urls)
|
||||
}
|
||||
if _, ok := urls[""]; ok {
|
||||
t.Fatalf("empty pickcode should be absent: %#v", urls)
|
||||
}
|
||||
mu.Lock()
|
||||
if len(requests) != 1 || requests[0] != "batch-pc-a,batch-pc-b,batch-pc-empty" {
|
||||
mu.Unlock()
|
||||
t.Fatalf("unexpected downurl requests: %v", requests)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
// 第二次调用全部命中缓存:不再发任何请求
|
||||
urls2, err := c.GetDownloadURLsBatch(context.Background(), []string{"batch-pc-a", "batch-pc-b"}, "")
|
||||
if err != nil {
|
||||
t.Fatalf("cached batch downurl: %v", err)
|
||||
}
|
||||
if urls2["batch-pc-a"] != "https://cdn/a.mkv" {
|
||||
t.Fatalf("cached url lost: %#v", urls2)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(requests) != 1 {
|
||||
t.Fatalf("cache hit should not issue requests, got %v", requests)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// 115 开放平台删除类 API:元数据覆盖上传前清理远端旧文件。
|
||||
package cloud115
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DeleteFiles 批量删除 115 文件(官方接口 POST /open/ufile/delete)。
|
||||
// 删除为异步执行,文件移入回收站。parentID 为待删除文件所在父目录 ID
|
||||
//(可选提示,空串省略)。fileIDs 中的空项自动忽略,全为空时直接返回成功。
|
||||
func (c *OpenClient) DeleteFiles(ctx context.Context, parentID string, fileIDs ...string) error {
|
||||
ids := make([]string, 0, len(fileIDs))
|
||||
for _, id := range fileIDs {
|
||||
if id = strings.TrimSpace(id); id != "" {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
params := map[string]string{"file_ids": strings.Join(ids, ",")}
|
||||
if parentID = strings.TrimSpace(parentID); parentID != "" {
|
||||
params["parent_id"] = parentID
|
||||
}
|
||||
resp, err := c.doAuthJSON(ctx, "POST", ProAPIBase+"/open/ufile/delete", params, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// doJSON 已把 state=false 转为错误返回,这里兜底防御响应外壳异常
|
||||
if !resp.State {
|
||||
return NewOpenAPIResponseError(resp.Code, resp.Errno, resp.Message, resp.Error, "115 删除文件失败")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -225,6 +225,62 @@ func (c *OpenClient) GetDownloadURLWithUA(ctx context.Context, pickCode, ua stri
|
||||
return first.URL.URL, nil
|
||||
}
|
||||
|
||||
// downurlBatchSize 单次批量换取直链的 pick_code 数上限。官方 /open/ufile/downurl
|
||||
// 支持逗号分隔多个 pick_code,批量可大幅降低元数据下载的换链请求量;大小取
|
||||
// 保守值,减小单个违规/异常文件导致整批失败的爆炸半径。
|
||||
const downurlBatchSize = 10
|
||||
|
||||
// GetDownloadURLsBatch 批量获取下载直链(pickcode → URL)。先查进程内缓存,
|
||||
// 仅对未命中的 pick_code 分片发起批量请求;单个分片失败时返回已解析的部分与
|
||||
// 错误,调用方对缺失项回退到逐个 GetDownloadURLWithUA。UA 语义与单个换取
|
||||
// 一致:直链绑定换取时的 UA,后续下载必须携带同一 UA。
|
||||
func (c *OpenClient) GetDownloadURLsBatch(ctx context.Context, pickCodes []string, ua string) (map[string]string, error) {
|
||||
ua = strings.TrimSpace(ua)
|
||||
out := make(map[string]string, len(pickCodes))
|
||||
seen := make(map[string]struct{}, len(pickCodes))
|
||||
missing := make([]string, 0, len(pickCodes))
|
||||
for _, pc := range pickCodes {
|
||||
pc = strings.TrimSpace(pc)
|
||||
if pc == "" {
|
||||
continue
|
||||
}
|
||||
if _, dup := seen[pc]; dup {
|
||||
continue
|
||||
}
|
||||
seen[pc] = struct{}{}
|
||||
if cached := GetDownloadURLCache(pc, ua); cached != "" {
|
||||
out[pc] = cached
|
||||
continue
|
||||
}
|
||||
missing = append(missing, pc)
|
||||
}
|
||||
for start := 0; start < len(missing); start += downurlBatchSize {
|
||||
end := start + downurlBatchSize
|
||||
if end > len(missing) {
|
||||
end = len(missing)
|
||||
}
|
||||
chunk := missing[start:end]
|
||||
params := map[string]string{"pick_code": strings.Join(chunk, ",")}
|
||||
resp, err := c.doAuthJSONWithUA(ctx, "POST", ProAPIBase+"/open/ufile/downurl", params, 1, ua)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
var data map[string]downloadURLData
|
||||
if err := json.Unmarshal(resp.Data, &data); err != nil {
|
||||
return out, fmt.Errorf("115: 解析下载地址失败:%w", err)
|
||||
}
|
||||
// 响应以文件 ID 为键,条目内的 pick_code 用于映射回请求侧
|
||||
for _, item := range data {
|
||||
if item.PickCode == "" || item.URL.URL == "" {
|
||||
continue
|
||||
}
|
||||
SetDownloadURLCache(item.PickCode, item.URL.URL, ua)
|
||||
out[item.PickCode] = item.URL.URL
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ─── 授权(设备码扫码) ──────────────────────────────────────────────────────
|
||||
|
||||
// QrCodeScanStatus 扫码状态。
|
||||
|
||||
Reference in New Issue
Block a user