mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
409 lines
10 KiB
Go
409 lines
10 KiB
Go
package pan
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// AlipanService 阿里云盘服务
|
|
type AlipanService struct {
|
|
*BasePanService
|
|
accessToken string
|
|
configMutex sync.RWMutex // 保护配置的读写锁
|
|
}
|
|
|
|
// 单例相关变量
|
|
var (
|
|
alipanInstance *AlipanService
|
|
alipanOnce sync.Once
|
|
)
|
|
|
|
// NewAlipanService 创建阿里云盘服务(单例模式)
|
|
func NewAlipanService(config *PanConfig) *AlipanService {
|
|
alipanOnce.Do(func() {
|
|
alipanInstance = &AlipanService{
|
|
BasePanService: NewBasePanService(config),
|
|
}
|
|
|
|
// 设置阿里云盘的默认请求头
|
|
alipanInstance.SetHeaders(map[string]string{
|
|
"Accept": "application/json, text/plain, */*",
|
|
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
|
|
"Content-Type": "application/json",
|
|
"Origin": "https://www.alipan.com",
|
|
"Priority": "u=1, i",
|
|
"Referer": "https://www.alipan.com/",
|
|
"Sec-Ch-Ua": `"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"`,
|
|
"Sec-Ch-Ua-Mobile": "?0",
|
|
"Sec-Ch-Ua-Platform": `"Windows"`,
|
|
"Sec-Fetch-Dest": "empty",
|
|
"Sec-Fetch-Mode": "cors",
|
|
"Sec-Fetch-Site": "same-site",
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36 Edg/126.0.0.0",
|
|
"X-Canary": "client=web,app=share,version=v2.3.1",
|
|
})
|
|
})
|
|
|
|
// 更新配置
|
|
alipanInstance.UpdateConfig(config)
|
|
|
|
return alipanInstance
|
|
}
|
|
|
|
// GetAlipanInstance 获取阿里云盘服务单例实例
|
|
func GetAlipanInstance() *AlipanService {
|
|
return NewAlipanService(nil)
|
|
}
|
|
|
|
// UpdateConfig 更新配置(线程安全)
|
|
func (a *AlipanService) UpdateConfig(config *PanConfig) {
|
|
if config == nil {
|
|
return
|
|
}
|
|
|
|
a.configMutex.Lock()
|
|
defer a.configMutex.Unlock()
|
|
|
|
a.config = config
|
|
}
|
|
|
|
// GetServiceType 获取服务类型
|
|
func (a *AlipanService) GetServiceType() ServiceType {
|
|
return Alipan
|
|
}
|
|
|
|
// Transfer 转存分享链接
|
|
func (a *AlipanService) Transfer(shareID string) (*TransferResult, error) {
|
|
// 读取配置(线程安全)
|
|
a.configMutex.RLock()
|
|
config := a.config
|
|
a.configMutex.RUnlock()
|
|
|
|
log.Printf("开始处理阿里云盘分享: %s", shareID)
|
|
|
|
// 获取access token
|
|
accessToken, err := a.manageAccessToken()
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("获取access_token失败: %v", err)), nil
|
|
}
|
|
|
|
// 设置Authorization头
|
|
a.SetHeader("Authorization", "Bearer "+accessToken)
|
|
|
|
// 获取分享信息
|
|
shareInfo, err := a.getAlipan1(shareID)
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("获取分享信息失败: %v", err)), nil
|
|
}
|
|
|
|
if config.IsType == 1 {
|
|
// 直接返回资源信息
|
|
return SuccessResult("检验成功", map[string]interface{}{
|
|
"title": shareInfo.ShareName,
|
|
"shareUrl": config.URL,
|
|
}), nil
|
|
}
|
|
|
|
// 获取share token
|
|
shareTokenResult, err := a.getAlipan2(shareID)
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("获取share_token失败: %v", err)), nil
|
|
}
|
|
|
|
// 确定存储路径
|
|
toPdirFid := "root" // 默认存储路径,可以从配置中读取
|
|
if config.ExpiredType == 2 {
|
|
toPdirFid = "temp" // 临时资源路径,可以从配置中读取
|
|
}
|
|
|
|
// 构建批量复制请求
|
|
batchRequests := make([]map[string]interface{}, 0)
|
|
for i, fileInfo := range shareInfo.FileInfos {
|
|
request := map[string]interface{}{
|
|
"body": map[string]interface{}{
|
|
"auto_rename": true,
|
|
"file_id": fileInfo.FileID,
|
|
"share_id": shareID,
|
|
"to_drive_id": "2008425230",
|
|
"to_parent_file_id": toPdirFid,
|
|
},
|
|
"headers": map[string]string{
|
|
"Content-Type": "application/json",
|
|
},
|
|
"id": fmt.Sprintf("%d", i),
|
|
"method": "POST",
|
|
"url": "/file/copy",
|
|
}
|
|
batchRequests = append(batchRequests, request)
|
|
}
|
|
|
|
batchData := map[string]interface{}{
|
|
"requests": batchRequests,
|
|
"resource": "file",
|
|
}
|
|
|
|
// 执行批量复制
|
|
copyResult, err := a.getAlipan3(batchData, shareTokenResult.ShareToken)
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("批量复制失败: %v", err)), nil
|
|
}
|
|
|
|
// 提取复制后的文件ID
|
|
fileIDList := make([]string, 0)
|
|
for _, response := range copyResult.Responses {
|
|
if response.Body.Code != "" {
|
|
return ErrorResult(fmt.Sprintf("复制失败: %s", response.Body.Message)), nil
|
|
}
|
|
fileIDList = append(fileIDList, response.Body.FileID)
|
|
}
|
|
|
|
// 创建分享
|
|
shareData := map[string]interface{}{
|
|
"drive_id": "2008425230",
|
|
"expiration": "",
|
|
"share_pwd": "",
|
|
"file_id_list": fileIDList,
|
|
}
|
|
|
|
shareResult, err := a.getAlipan4(shareData)
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("创建分享失败: %v", err)), nil
|
|
}
|
|
|
|
return SuccessResult("转存成功", map[string]interface{}{
|
|
"shareUrl": shareResult.ShareURL,
|
|
"title": shareResult.ShareTitle,
|
|
"fid": shareResult.FileIDList,
|
|
}), nil
|
|
}
|
|
|
|
// GetFiles 获取文件列表
|
|
func (a *AlipanService) GetFiles(pdirFid string) (*TransferResult, error) {
|
|
// 获取access token
|
|
accessToken, err := a.manageAccessToken()
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("获取access_token失败: %v", err)), nil
|
|
}
|
|
|
|
// 设置Authorization头
|
|
a.SetHeader("Authorization", "Bearer "+accessToken)
|
|
|
|
if pdirFid == "" {
|
|
pdirFid = "root"
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"all": false,
|
|
"drive_id": "2008425230",
|
|
"fields": "*",
|
|
"limit": 100,
|
|
"order_by": "updated_at",
|
|
"order_direction": "DESC",
|
|
"parent_file_id": pdirFid,
|
|
"url_expire_sec": 14400,
|
|
}
|
|
|
|
respData, err := a.HTTPPost("https://api.aliyundrive.com/adrive/v3/file/list", data, nil)
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("获取文件列表失败: %v", err)), nil
|
|
}
|
|
|
|
var response struct {
|
|
Message string `json:"message"`
|
|
Items []interface{} `json:"items"`
|
|
}
|
|
|
|
if err := json.Unmarshal(respData, &response); err != nil {
|
|
return ErrorResult("解析响应失败"), nil
|
|
}
|
|
|
|
if response.Message != "" {
|
|
return ErrorResult(response.Message), nil
|
|
}
|
|
|
|
return SuccessResult("获取成功", response.Items), nil
|
|
}
|
|
|
|
// DeleteFiles 删除文件
|
|
func (a *AlipanService) DeleteFiles(fileList []string) (*TransferResult, error) {
|
|
// 获取access token
|
|
accessToken, err := a.manageAccessToken()
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("获取access_token失败: %v", err)), nil
|
|
}
|
|
|
|
// 设置Authorization头
|
|
a.SetHeader("Authorization", "Bearer "+accessToken)
|
|
|
|
data := map[string]interface{}{
|
|
"drive_id": "2008425230",
|
|
"file_id_list": fileList,
|
|
}
|
|
|
|
_, err = a.HTTPPost("https://api.aliyundrive.com/adrive/v3/file/delete", data, nil)
|
|
if err != nil {
|
|
return ErrorResult(fmt.Sprintf("删除文件失败: %v", err)), nil
|
|
}
|
|
|
|
return SuccessResult("删除成功", nil), nil
|
|
}
|
|
|
|
// getAlipan1 通过分享id获取file_id
|
|
func (a *AlipanService) getAlipan1(shareID string) (*AlipanShareInfo, error) {
|
|
data := map[string]interface{}{
|
|
"share_id": shareID,
|
|
}
|
|
|
|
headers := map[string]string{
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
// 临时设置headers
|
|
originalHeaders := a.headers
|
|
a.SetHeaders(headers)
|
|
defer func() { a.headers = originalHeaders }()
|
|
|
|
respData, err := a.HTTPPost("https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous", data, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result AlipanShareInfo
|
|
if err := json.Unmarshal(respData, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// getAlipan2 通过分享id获取X-Share-Token
|
|
func (a *AlipanService) getAlipan2(shareID string) (*AlipanShareToken, error) {
|
|
data := map[string]interface{}{
|
|
"share_id": shareID,
|
|
}
|
|
|
|
respData, err := a.HTTPPost("https://api.aliyundrive.com/v2/share_link/get_share_token", data, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result AlipanShareToken
|
|
if err := json.Unmarshal(respData, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// getAlipan3 批量复制
|
|
func (a *AlipanService) getAlipan3(batchData map[string]interface{}, shareToken string) (*AlipanBatchResult, error) {
|
|
// 设置X-Share-Token头
|
|
a.SetHeader("X-Share-Token", shareToken)
|
|
|
|
respData, err := a.HTTPPost("https://api.aliyundrive.com/adrive/v4/batch", batchData, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result AlipanBatchResult
|
|
if err := json.Unmarshal(respData, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// getAlipan4 创建分享
|
|
func (a *AlipanService) getAlipan4(shareData map[string]interface{}) (*AlipanShareResult, error) {
|
|
respData, err := a.HTTPPost("https://api.aliyundrive.com/adrive/v3/share_link/create", shareData, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result AlipanShareResult
|
|
if err := json.Unmarshal(respData, &result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// manageAccessToken 管理access token
|
|
func (a *AlipanService) manageAccessToken() (string, error) {
|
|
if a.accessToken != "" {
|
|
return a.accessToken, nil
|
|
}
|
|
|
|
// 从文件读取token
|
|
tokenFile := filepath.Join("config", "alipan_access_token.json")
|
|
|
|
// 检查token文件是否存在
|
|
if _, err := os.Stat(tokenFile); os.IsNotExist(err) {
|
|
// 获取新的access token
|
|
return a.getNewAccessToken()
|
|
}
|
|
|
|
// 读取token文件
|
|
data, err := os.ReadFile(tokenFile)
|
|
if err != nil {
|
|
return a.getNewAccessToken()
|
|
}
|
|
|
|
var tokenInfo struct {
|
|
AccessToken string `json:"access_token"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &tokenInfo); err != nil {
|
|
return a.getNewAccessToken()
|
|
}
|
|
|
|
// 检查token是否过期
|
|
if time.Now().After(tokenInfo.ExpiresAt) {
|
|
return a.getNewAccessToken()
|
|
}
|
|
|
|
a.accessToken = tokenInfo.AccessToken
|
|
return a.accessToken, nil
|
|
}
|
|
|
|
// getNewAccessToken 获取新的access token
|
|
func (a *AlipanService) getNewAccessToken() (string, error) {
|
|
// 这里需要实现获取新token的逻辑
|
|
// 可能需要用户登录或者使用refresh token
|
|
return "", fmt.Errorf("需要实现获取新access token的逻辑")
|
|
}
|
|
|
|
// 定义阿里云盘相关的结构体
|
|
type AlipanShareInfo struct {
|
|
ShareName string `json:"share_name"`
|
|
FileInfos []struct {
|
|
FileID string `json:"file_id"`
|
|
} `json:"file_infos"`
|
|
}
|
|
|
|
type AlipanShareToken struct {
|
|
ShareToken string `json:"share_token"`
|
|
}
|
|
|
|
type AlipanBatchResult struct {
|
|
Responses []struct {
|
|
Body struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
FileID string `json:"file_id"`
|
|
} `json:"body"`
|
|
} `json:"responses"`
|
|
}
|
|
|
|
type AlipanShareResult struct {
|
|
ShareURL string `json:"share_url"`
|
|
ShareTitle string `json:"share_title"`
|
|
FileIDList []string `json:"file_id_list"`
|
|
}
|