diff --git a/utils/pan/PAN_FACTORY_IMPLEMENTATION.md b/common/PAN_FACTORY_IMPLEMENTATION.md similarity index 100% rename from utils/pan/PAN_FACTORY_IMPLEMENTATION.md rename to common/PAN_FACTORY_IMPLEMENTATION.md diff --git a/utils/pan/README_PAN_FACTORY.md b/common/README_PAN_FACTORY.md similarity index 100% rename from utils/pan/README_PAN_FACTORY.md rename to common/README_PAN_FACTORY.md diff --git a/utils/pan/SERVICE_SINGLETON_ANALYSIS.md b/common/SERVICE_SINGLETON_ANALYSIS.md similarity index 100% rename from utils/pan/SERVICE_SINGLETON_ANALYSIS.md rename to common/SERVICE_SINGLETON_ANALYSIS.md diff --git a/utils/pan/SINGLETON_ANALYSIS.md b/common/SINGLETON_ANALYSIS.md similarity index 100% rename from utils/pan/SINGLETON_ANALYSIS.md rename to common/SINGLETON_ANALYSIS.md diff --git a/utils/pan/alipan.go b/common/alipan.go similarity index 100% rename from utils/pan/alipan.go rename to common/alipan.go diff --git a/utils/pan/baidu_pan.go b/common/baidu_pan.go similarity index 100% rename from utils/pan/baidu_pan.go rename to common/baidu_pan.go diff --git a/utils/pan/base_pan.go b/common/base_pan.go similarity index 100% rename from utils/pan/base_pan.go rename to common/base_pan.go diff --git a/utils/pan/pan_example.go b/common/pan_example.go similarity index 100% rename from utils/pan/pan_example.go rename to common/pan_example.go diff --git a/utils/pan/pan_factory.go b/common/pan_factory.go similarity index 100% rename from utils/pan/pan_factory.go rename to common/pan_factory.go diff --git a/utils/pan/pan_factory_test.go b/common/pan_factory_test.go similarity index 100% rename from utils/pan/pan_factory_test.go rename to common/pan_factory_test.go diff --git a/utils/pan/quark_pan.go b/common/quark_pan.go similarity index 80% rename from utils/pan/quark_pan.go rename to common/quark_pan.go index d8b39ae..62d5d5a 100644 --- a/utils/pan/quark_pan.go +++ b/common/quark_pan.go @@ -90,15 +90,6 @@ func (q *QuarkPanService) Transfer(shareID string) (*TransferResult, error) { return ErrorResult(fmt.Sprintf("获取stoken失败: %v", err)), nil } - if config.IsType == 1 { - // 直接返回资源信息 - return SuccessResult("检验成功", map[string]interface{}{ - "title": stokenResult.Title, - "shareUrl": config.URL, - "stoken": stokenResult.Stoken, - }), nil - } - stoken = strings.ReplaceAll(stokenResult.Stoken, " ", "+") } else { stoken = strings.ReplaceAll(config.Stoken, " ", "+") @@ -110,6 +101,42 @@ func (q *QuarkPanService) Transfer(shareID string) (*TransferResult, error) { return ErrorResult(fmt.Sprintf("获取分享详情失败: %v", err)), nil } + if config.IsType == 1 { + // 遍历 资源目录结构 + for _, item := range shareResult.List { + // 获取文件信息 + fileList, err := q.getDirFile(item.Fid) + if err != nil { + log.Printf("获取目录文件失败: %v", err) + continue + } + + // 处理文件列表 + if fileList != nil { + log.Printf("目录 %s 包含 %d 个文件/文件夹", item.Fid, len(fileList)) + + // 遍历所有文件,可以在这里添加具体的处理逻辑 + for _, file := range fileList { + if fileName, ok := file["file_name"].(string); ok { + if fileType, ok := file["file_type"].(float64); ok { + fileTypeStr := "文件" + if fileType == 1 { + fileTypeStr = "目录" + } + log.Printf(" - %s (%s)", fileName, fileTypeStr) + } + } + } + } + } + + // 直接返回资源信息 + return SuccessResult("检验成功", map[string]interface{}{ + "title": shareResult.Share.Title, + "shareUrl": config.URL, + }), nil + } + // 提取文件信息 fidList := make([]string, 0) fidTokenList := make([]string, 0) @@ -280,18 +307,23 @@ func (q *QuarkPanService) getStoken(shareID string) (*StokenResult, error) { // getShare 获取分享详情 func (q *QuarkPanService) getShare(shareID, stoken string) (*ShareResult, error) { - data := map[string]interface{}{ - "pwd_id": shareID, - "stoken": stoken, - } - queryParams := map[string]string{ - "pr": "ucpro", - "fr": "pc", - "uc_param_str": "", + "pr": "ucpro", + "fr": "pc", + "uc_param_str": "", + "pwd_id": shareID, + "stoken": stoken, + "pdir_fid": "0", + "force": "0", + "_page": "1", + "_size": "100", + "_fetch_banner": "1", + "_fetch_share": "1", + "_fetch_total": "1", + "_sort": "file_type:asc,updated_at:desc", } - respData, err := q.HTTPPost("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", data, queryParams) + respData, err := q.HTTPGet("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", queryParams) if err != nil { return nil, err } @@ -481,6 +513,67 @@ func (q *QuarkPanService) deleteAdFiles(pdirFid string) error { return nil } +// getDirFile 获取指定文件夹的文件列表 +func (q *QuarkPanService) getDirFile(pdirFid string) ([]map[string]interface{}, error) { + log.Printf("正在遍历父文件夹: %s", pdirFid) + + queryParams := map[string]string{ + "pr": "ucpro", + "fr": "pc", + "uc_param_str": "", + "pdir_fid": pdirFid, + "_page": "1", + "_size": "50", + "_fetch_total": "1", + "_fetch_sub_dirs": "0", + "_sort": "updated_at:desc", + } + + respData, err := q.HTTPGet("https://drive-pc.quark.cn/1/clouddrive/file/sort", queryParams) + if err != nil { + log.Printf("获取目录文件失败: %v", err) + return nil, err + } + + var response struct { + Status int `json:"status"` + Message string `json:"message"` + Data struct { + List []map[string]interface{} `json:"list"` + } `json:"data"` + } + + if err := json.Unmarshal(respData, &response); err != nil { + log.Printf("解析目录文件响应失败: %v", err) + return nil, err + } + + if response.Status != 200 { + return nil, fmt.Errorf(response.Message) + } + + // 递归处理子目录 + var allFiles []map[string]interface{} + for _, item := range response.Data.List { + // 添加当前文件/目录 + allFiles = append(allFiles, item) + + // 如果是目录,递归获取子目录内容 + if fileType, ok := item["file_type"].(float64); ok && fileType == 1 { // 1表示目录 + if fid, ok := item["fid"].(string); ok { + subFiles, err := q.getDirFile(fid) + if err != nil { + log.Printf("获取子目录 %s 失败: %v", fid, err) + continue + } + allFiles = append(allFiles, subFiles...) + } + } + } + + return allFiles, nil +} + // 定义各种结果结构体 type StokenResult struct { Stoken string `json:"stoken"` diff --git a/utils/pan/service_singleton_test.go b/common/service_singleton_test.go similarity index 100% rename from utils/pan/service_singleton_test.go rename to common/service_singleton_test.go diff --git a/utils/pan/singleton_test.go b/common/singleton_test.go similarity index 100% rename from utils/pan/singleton_test.go rename to common/singleton_test.go diff --git a/utils/pan/uc_pan.go b/common/uc_pan.go similarity index 100% rename from utils/pan/uc_pan.go rename to common/uc_pan.go diff --git a/utils/url_checker.go b/common/utils/url_checker.go similarity index 99% rename from utils/url_checker.go rename to common/utils/url_checker.go index 98f2c83..af592a1 100644 --- a/utils/url_checker.go +++ b/common/utils/url_checker.go @@ -1,4 +1,4 @@ -package utils +package common import ( "encoding/json" diff --git a/doc/QUARK_GETSHARE_FIX.md b/doc/QUARK_GETSHARE_FIX.md new file mode 100644 index 0000000..1e340e8 --- /dev/null +++ b/doc/QUARK_GETSHARE_FIX.md @@ -0,0 +1,166 @@ +# 夸克网盘 getShare 方法修复 + +## 问题描述 + +在夸克网盘的 `getShare` 函数中遇到了 HTTP 405 错误: + +``` +HTTP请求失败: 405, {"timestamp":1752368941648,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/1/clouddrive/share/sharepage/detail"} +``` + +## 问题分析 + +### 错误原因 +- 当前Go代码使用 `HTTPPost` 方法请求 `/1/clouddrive/share/sharepage/detail` 接口 +- 但服务器返回 405 错误,表示不支持 POST 方法 +- 需要改为 GET 请求 + +### 对比原始PHP代码 +通过对比 `demo/pan/QuarkPan.php` 中的 `getShare` 方法: + +```php +public function getShare($pwd_id,$stoken) +{ + $urlData = array(); + $queryParams = [ + "pr" => "ucpro", + "fr" => "pc", + "uc_param_str" => "", + "pwd_id" => $pwd_id, + "stoken" => $stoken, + "pdir_fid" => "0", + "force" => "0", + "_page" => "1", + "_size" => "100", + "_fetch_banner" => "1", + "_fetch_share" => "1", + "_fetch_total" => "1", + "_sort" => "file_type:asc,updated_at:desc" + ]; + return $this->executeApiRequest( + "https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", + "GET", // 使用 GET 方法 + $urlData, + $queryParams + ); +} +``` + +## 修复方案 + +### 修复前的代码 +```go +// 修复前:使用 POST 请求 +func (q *QuarkPanService) getShare(shareID, stoken string) (*ShareResult, error) { + data := map[string]interface{}{ + "pwd_id": shareID, + "stoken": stoken, + } + + queryParams := map[string]string{ + "pr": "ucpro", + "fr": "pc", + "uc_param_str": "", + } + + respData, err := q.HTTPPost("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", data, queryParams) + // ... +} +``` + +### 修复后的代码 +```go +// 修复后:使用 GET 请求,参数放在 URL 中 +func (q *QuarkPanService) getShare(shareID, stoken string) (*ShareResult, error) { + queryParams := map[string]string{ + "pr": "ucpro", + "fr": "pc", + "uc_param_str": "", + "pwd_id": shareID, + "stoken": stoken, + "pdir_fid": "0", + "force": "0", + "_page": "1", + "_size": "100", + "_fetch_banner": "1", + "_fetch_share": "1", + "_fetch_total": "1", + "_sort": "file_type:asc,updated_at:desc", + } + + respData, err := q.HTTPGet("https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail", queryParams) + // ... +} +``` + +## 修复内容 + +### 1. 请求方法修改 +- **从 POST 改为 GET**:`HTTPPost` → `HTTPGet` +- **参数传递方式**:从请求体改为 URL 查询参数 + +### 2. 参数结构调整 +- **移除请求体数据**:不再使用 `data` 参数 +- **添加完整查询参数**:按照PHP代码添加所有必要的查询参数 +- **参数顺序**:保持与PHP代码一致的参数顺序 + +### 3. 参数说明 +| 参数 | 说明 | 值 | +|------|------|-----| +| `pr` | 产品标识 | `ucpro` | +| `fr` | 来源标识 | `pc` | +| `uc_param_str` | UC参数 | 空字符串 | +| `pwd_id` | 分享ID | 从URL提取 | +| `stoken` | 安全令牌 | 从getStoken获取 | +| `pdir_fid` | 父目录ID | `0` | +| `force` | 强制标志 | `0` | +| `_page` | 页码 | `1` | +| `_size` | 页面大小 | `100` | +| `_fetch_banner` | 获取横幅 | `1` | +| `_fetch_share` | 获取分享信息 | `1` | +| `_fetch_total` | 获取总数 | `1` | +| `_sort` | 排序方式 | `file_type:asc,updated_at:desc` | + +## 验证结果 + +### 编译测试 +```bash +go build -o res_db . +# ✅ 编译成功 +``` + +### 功能测试 +```bash +go test ./utils/pan -v +# ✅ 所有测试通过 +``` + +### 预期效果 +- **解决 405 错误**:使用正确的 GET 请求方法 +- **保持功能完整**:所有参数都正确传递 +- **兼容性良好**:与原始PHP代码行为一致 + +## 经验总结 + +### 1. API 兼容性 +- 在翻译代码时,需要仔细对比原始实现的请求方法 +- 不同语言的HTTP客户端可能有不同的默认行为 +- 需要确保请求方法、参数位置、参数名称都完全一致 + +### 2. 错误排查 +- HTTP 405 错误通常表示请求方法不正确 +- 对比原始代码是排查此类问题的最佳方法 +- 需要检查请求方法、URL、参数等多个方面 + +### 3. 最佳实践 +- **保持一致性**:与原始实现保持完全一致 +- **完整参数**:不要遗漏任何必要的参数 +- **测试验证**:修复后要进行充分的测试 + +## 相关文件 + +- `utils/pan/quark_pan.go` - 修复的文件 +- `demo/pan/QuarkPan.php` - 原始PHP实现参考 +- `utils/pan/base_pan.go` - 基础HTTP方法实现 + +这次修复确保了夸克网盘的 `getShare` 方法与原始PHP实现完全一致,解决了HTTP 405错误问题。 \ No newline at end of file diff --git a/utils/scheduler.go b/utils/scheduler.go index 93141ea..377c504 100644 --- a/utils/scheduler.go +++ b/utils/scheduler.go @@ -2,9 +2,10 @@ package utils import ( "log" + panutils "res_db/common" + commonutils "res_db/common/utils" "res_db/db/entity" "res_db/db/repo" - panutils "res_db/utils/pan" "strings" "sync" "time" @@ -315,11 +316,23 @@ func (s *Scheduler) convertReadyResourceToResource(readyResource entity.ReadyRes log.Printf("检测到服务类型: %s, 分享ID: %s", serviceType.String(), shareID) + // 不是夸克,直接保存, + if serviceType != panutils.Quark { + // 检测是否有效 + checkResult, _ := commonutils.CheckURL(readyResource.URL) + if !checkResult.Status { + log.Printf("链接无效: %s", readyResource.URL) + return nil + } + + // 入库 + } + // 准备配置 config := &panutils.PanConfig{ URL: readyResource.URL, Code: "", // 可以从readyResource中获取 - IsType: 0, // 转存并分享后的资源信息 + IsType: 1, // 转存并分享后的资源信息 0 转存后分享, 1 只获取基本信息 ExpiredType: 1, // 永久分享 AdFid: "", Stoken: "", @@ -333,45 +346,45 @@ func (s *Scheduler) convertReadyResourceToResource(readyResource entity.ReadyRes } // 阿里云盘特殊处理:检查URL有效性 - if serviceType == panutils.Alipan { - checkResult, _ := CheckURL(readyResource.URL) - if !checkResult.Status { - log.Printf("阿里云盘链接无效: %s", readyResource.URL) - return nil - } + // if serviceType == panutils.Alipan { + // checkResult, _ := CheckURL(readyResource.URL) + // if !checkResult.Status { + // log.Printf("阿里云盘链接无效: %s", readyResource.URL) + // return nil + // } - // 如果有标题,直接创建资源 - if readyResource.Title != nil && *readyResource.Title != "" { - resource := &entity.Resource{ - Title: *readyResource.Title, - Description: readyResource.Description, - URL: readyResource.URL, - PanID: s.determinePanID(readyResource.URL), - IsValid: true, - IsPublic: true, - } + // // 如果有标题,直接创建资源 + // if readyResource.Title != nil && *readyResource.Title != "" { + // resource := &entity.Resource{ + // Title: *readyResource.Title, + // Description: readyResource.Description, + // URL: readyResource.URL, + // PanID: s.determinePanID(readyResource.URL), + // IsValid: true, + // IsPublic: true, + // } - // 如果有分类信息,尝试查找或创建分类 - if readyResource.Category != "" { - categoryID, err := s.getOrCreateCategory(readyResource.Category) - if err == nil { - resource.CategoryID = &categoryID - } - } + // // 如果有分类信息,尝试查找或创建分类 + // if readyResource.Category != "" { + // categoryID, err := s.getOrCreateCategory(readyResource.Category) + // if err == nil { + // resource.CategoryID = &categoryID + // } + // } - return s.resourceRepo.Create(resource) - } - } + // return s.resourceRepo.Create(resource) + // } + // } // 统一处理:尝试转存获取标题 result, err := panService.Transfer(shareID) if err != nil { - log.Printf("网盘转存失败: %v", err) + log.Printf("网盘信息获取失败: %v", err) return err } if !result.Success { - log.Printf("网盘转存失败: %s", result.Message) + log.Printf("网盘信息获取失败: %s", result.Message) return nil }