mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: php code
This commit is contained in:
@@ -26,6 +26,7 @@ type ResourceRepository interface {
|
|||||||
GetLatestResources(limit int) ([]entity.Resource, error)
|
GetLatestResources(limit int) ([]entity.Resource, error)
|
||||||
GetCachedLatestResources(limit int) ([]entity.Resource, error)
|
GetCachedLatestResources(limit int) ([]entity.Resource, error)
|
||||||
InvalidateCache() error
|
InvalidateCache() error
|
||||||
|
FindExists(url string, excludeID ...uint) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceRepositoryImpl Resource的Repository实现
|
// ResourceRepositoryImpl Resource的Repository实现
|
||||||
@@ -256,3 +257,20 @@ func (r *ResourceRepositoryImpl) InvalidateCache() error {
|
|||||||
r.cache = make(map[string]interface{})
|
r.cache = make(map[string]interface{})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindExists 检查是否存在相同URL的资源
|
||||||
|
func (r *ResourceRepositoryImpl) FindExists(url string, excludeID ...uint) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
query := r.db.Model(&entity.Resource{}).Where("url = ?", url)
|
||||||
|
|
||||||
|
// 如果有排除ID,则排除该记录(用于更新时排除自己)
|
||||||
|
if len(excludeID) > 0 {
|
||||||
|
query = query.Where("id != ?", excludeID[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Count(&count).Error
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
|
|||||||
123
demo/Transfer.php
Normal file
123
demo/Transfer.php
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
namespace netdisk;
|
||||||
|
|
||||||
|
class Transfer
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// 原 Open 类的构造函数内容
|
||||||
|
$this->url = ""; // 资源地址
|
||||||
|
$this->expired_type = 1; //有效期 1分享永久 2临时
|
||||||
|
$this->ad_fid = ""; //夸克专用 - 分享时带上这个文件的fid
|
||||||
|
$this->code = ""; //分享码
|
||||||
|
$this->isType = 0; //0 转存并分享后的资源信息 1直接获取资源信息
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFiles($type=0,$pdir_fid=0)
|
||||||
|
{
|
||||||
|
if($type == 1){
|
||||||
|
//阿里
|
||||||
|
$pan = new \netdisk\pan\AlipanPan();
|
||||||
|
return $pan->getFiles($pdir_fid);
|
||||||
|
} else if($type == 2){
|
||||||
|
//百度网盘
|
||||||
|
$pan = new \netdisk\pan\BaiduPan();
|
||||||
|
return $pan->getFiles($pdir_fid);
|
||||||
|
} else if($type == 3){
|
||||||
|
//UC
|
||||||
|
$pan = new \netdisk\pan\UcPan();
|
||||||
|
return $pan->getFiles($pdir_fid);
|
||||||
|
} else {
|
||||||
|
//夸克
|
||||||
|
$pan = new \netdisk\pan\QuarkPan();
|
||||||
|
return $pan->getFiles($pdir_fid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transfer($urlData = [])
|
||||||
|
{
|
||||||
|
$url = $urlData['url']??'';
|
||||||
|
$config = [
|
||||||
|
'isType' => $urlData['isType'] ?? input('isType') ?? 0,
|
||||||
|
'url' => $url,
|
||||||
|
'code' => $urlData['code'] ?? input('code') ?? '',
|
||||||
|
'expired_type' => $urlData['expired_type'] ?? input('expired_type') ?? 1,
|
||||||
|
'ad_fid' => $urlData['ad_fid'] ?? input('ad_fid') ?? "",
|
||||||
|
'stoken' => $urlData['stoken'] ?? '',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (strpos($url, '?entry=') !== false) {
|
||||||
|
$entry = preg_match('/\?entry=([^&]+)/', $url, $matches) ? $matches[1] : '';
|
||||||
|
$url = preg_match('/.*(?=\?entry=)/', $url, $matches) ? $matches[0] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$substring = strstr($url, 's/');
|
||||||
|
if ($substring !== false) {
|
||||||
|
$pwd_id = substr($substring, 2); // 去除 's/' 部分
|
||||||
|
} else {
|
||||||
|
return jerr2("资源地址格式有误");
|
||||||
|
}
|
||||||
|
|
||||||
|
$patterns = [
|
||||||
|
'pan.quark.cn' => 0,
|
||||||
|
'www.alipan.com' => 1,
|
||||||
|
'www.aliyundrive.com' => 1,
|
||||||
|
'pan.baidu.com' => 2,
|
||||||
|
'drive.uc.cn' => 3,
|
||||||
|
'fast.uc.cn' => 3,
|
||||||
|
// 'pan.xunlei.com' => 4,
|
||||||
|
];
|
||||||
|
|
||||||
|
$url_type = -1; // 默认值为 -1
|
||||||
|
foreach ($patterns as $pattern => $type) {
|
||||||
|
if (strpos($url, $pattern) !== false) {
|
||||||
|
$url_type = $type;
|
||||||
|
break; // 一旦匹配成功,退出循环
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->url = $url;
|
||||||
|
|
||||||
|
if ($url_type == 0) {
|
||||||
|
//夸克
|
||||||
|
$pan = new \netdisk\pan\QuarkPan($config);
|
||||||
|
return $pan->transfer(strtok($pwd_id, '#'));
|
||||||
|
} else if($url_type == 1){
|
||||||
|
//阿里
|
||||||
|
$pan = new \netdisk\pan\AlipanPan($config);
|
||||||
|
return $pan->transfer(strtok($pwd_id, '#'));
|
||||||
|
} else if($url_type == 2){
|
||||||
|
//百度网盘
|
||||||
|
$pan = new \netdisk\pan\BaiduPan($config);
|
||||||
|
return $pan->transfer(strtok($pwd_id, '#'));
|
||||||
|
} else if($url_type == 3){
|
||||||
|
//UC
|
||||||
|
$pan = new \netdisk\pan\UcPan($config);
|
||||||
|
return $pan->transfer(strtok($pwd_id, '#'));
|
||||||
|
} else {
|
||||||
|
return jerr2("资源地址格式有误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function deletepdirFid($type=0,$filelist)
|
||||||
|
{
|
||||||
|
if($type == 1){
|
||||||
|
//阿里
|
||||||
|
$pan = new \netdisk\pan\AlipanPan();
|
||||||
|
return $pan->deletepdirFid($filelist);
|
||||||
|
} else if($type == 2){
|
||||||
|
//百度网盘
|
||||||
|
$pan = new \netdisk\pan\BaiduPan();
|
||||||
|
return $pan->deletepdirFid($filelist);
|
||||||
|
} else if($type == 3){
|
||||||
|
//UC
|
||||||
|
$pan = new \netdisk\pan\UcPan();
|
||||||
|
return $pan->deletepdirFid(filelist);
|
||||||
|
} else {
|
||||||
|
//夸克
|
||||||
|
$pan = new \netdisk\pan\QuarkPan();
|
||||||
|
return $pan->deletepdirFid($filelist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
308
demo/pan/AlipanPan.php
Normal file
308
demo/pan/AlipanPan.php
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
<?php
|
||||||
|
namespace netdisk\pan;
|
||||||
|
|
||||||
|
class AlipanPan extends BasePan
|
||||||
|
{
|
||||||
|
public function __construct($config = [])
|
||||||
|
{
|
||||||
|
parent::__construct($config);
|
||||||
|
$this->urlHeader = [
|
||||||
|
'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',
|
||||||
|
'Authorization: ',
|
||||||
|
'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'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFiles($pdir_fid=0)
|
||||||
|
{
|
||||||
|
$tokenFile = __DIR__ . '/access_token.json';
|
||||||
|
$access_token = $this->manageAccessToken($tokenFile);
|
||||||
|
if(empty($access_token)){
|
||||||
|
return jerr2('登录状态异常,获取access_token失败');
|
||||||
|
}
|
||||||
|
foreach ($this->urlHeader as &$header) {
|
||||||
|
if (str_starts_with($header, 'Authorization: ')) {
|
||||||
|
$header = 'Authorization: Bearer ' . $access_token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($pdir_fid === 0){
|
||||||
|
$pdir_fid = 'root';
|
||||||
|
}
|
||||||
|
|
||||||
|
$urlData = array(
|
||||||
|
'all' => false,
|
||||||
|
'drive_id' => '2008425230',
|
||||||
|
'fields' => "*",
|
||||||
|
'limit' => 100,
|
||||||
|
'order_by' => "updated_at",
|
||||||
|
'order_direction' => "DESC",
|
||||||
|
'parent_file_id' => $pdir_fid,
|
||||||
|
'url_expire_sec' => 14400,
|
||||||
|
);
|
||||||
|
|
||||||
|
$res = curlHelper("https://api.aliyundrive.com/adrive/v3/file/list", "POST", json_encode($urlData),$this->urlHeader)['body'];
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
if(!empty($res['message'])){
|
||||||
|
return jerr2($res['message']);
|
||||||
|
}
|
||||||
|
return jok2('获取成功',$res['items']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transfer($share_id)
|
||||||
|
{
|
||||||
|
$tokenFile = __DIR__ . '/access_token.json';
|
||||||
|
$access_token = $this->manageAccessToken($tokenFile);
|
||||||
|
if(empty($access_token)){
|
||||||
|
return jerr2('登录状态异常,获取access_token失败');
|
||||||
|
}
|
||||||
|
foreach ($this->urlHeader as &$header) {
|
||||||
|
if (str_starts_with($header, 'Authorization: ')) {
|
||||||
|
$header = 'Authorization: Bearer ' . $access_token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
$res = $this->getAlipan1($share_id);
|
||||||
|
if(!isset($res['file_infos'])){
|
||||||
|
return jerr2($res['message']);
|
||||||
|
}
|
||||||
|
$infos = $res;
|
||||||
|
|
||||||
|
if($this->isType == 1){
|
||||||
|
$urls['title'] = $infos['share_name'];
|
||||||
|
$urls['share_url'] = $this->url;
|
||||||
|
return jok2('检验成功', $urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
//通过分享id获取file_id
|
||||||
|
$file_infos = $infos['file_infos'];
|
||||||
|
//通过分享id获取X-Share-Token
|
||||||
|
$res = $this->getAlipan2($share_id);
|
||||||
|
if(!isset($res['share_token'])){
|
||||||
|
return jerr2($res['message']);
|
||||||
|
}
|
||||||
|
$share_token = $res['share_token'];
|
||||||
|
|
||||||
|
$to_pdir_fid = Config('qfshop.ali_file'); //默认存储路径
|
||||||
|
if($this->expired_type == 2){
|
||||||
|
$to_pdir_fid = Config('qfshop.ali_file_time'); //临时资源路径
|
||||||
|
}
|
||||||
|
|
||||||
|
$data3['requests'] = [];
|
||||||
|
$data3['resource'] = 'file';
|
||||||
|
|
||||||
|
foreach ($file_infos as $key=>$value) {
|
||||||
|
$data3['requests'][$key]['body']['auto_rename'] = true;
|
||||||
|
$data3['requests'][$key]['body']['file_id'] = $value['file_id'];
|
||||||
|
$data3['requests'][$key]['body']['share_id'] = $share_id;
|
||||||
|
$data3['requests'][$key]['body']['to_drive_id'] = '2008425230';
|
||||||
|
$data3['requests'][$key]['body']['to_parent_file_id'] = $to_pdir_fid;
|
||||||
|
$data3['requests'][$key]['headers']['Content-Type'] = 'application/json';
|
||||||
|
$data3['requests'][$key]['id'] = $key.'';
|
||||||
|
$data3['requests'][$key]['method'] = 'POST';
|
||||||
|
$data3['requests'][$key]['url'] = '/file/copy';
|
||||||
|
}
|
||||||
|
|
||||||
|
//保存
|
||||||
|
$res = $this->getAlipan3($data3,$share_token);
|
||||||
|
if (!isset($res['responses'])) {
|
||||||
|
return jerr2($res['message'] ?? '请求失败,无响应数据');
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $res['responses'][0];
|
||||||
|
$body = $response['body'];
|
||||||
|
if (isset($body['code'])) {
|
||||||
|
return jerr2($body['message'] ?? '请求失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
$responses = $res['responses'];
|
||||||
|
|
||||||
|
$data4['drive_id'] = '2008425230';
|
||||||
|
$data4['expiration'] = '';
|
||||||
|
$data4['share_pwd'] = '';
|
||||||
|
$data4['file_id_list'] = [];
|
||||||
|
|
||||||
|
foreach ($responses as $key=>$value){
|
||||||
|
$data4['file_id_list'][] = $value['body']['file_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
//分享
|
||||||
|
$res = $this->getAlipan4($data4);
|
||||||
|
if(!isset($res['share_url'])){
|
||||||
|
return jerr2($res['message']??'转存失败4');
|
||||||
|
}
|
||||||
|
$share = $res;
|
||||||
|
|
||||||
|
$data['share_url'] = $share['share_url'];
|
||||||
|
$data['title'] = $share['share_title'];
|
||||||
|
$data['fid'] = $share['file_id_list'];
|
||||||
|
|
||||||
|
return jok2('转存成功', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里-0-通过分享id获取file_id
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getAlipan1($share_id)
|
||||||
|
{
|
||||||
|
$urlData = [
|
||||||
|
'share_id' => $share_id,
|
||||||
|
];
|
||||||
|
$urlHeader = array(
|
||||||
|
'Content-Type: application/json',
|
||||||
|
);
|
||||||
|
$res = curlHelper("https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous", "POST", json_encode($urlData),$urlHeader)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里-0-通过分享id获取X-Share-Token
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getAlipan2($share_id)
|
||||||
|
{
|
||||||
|
$urlData = array(
|
||||||
|
'share_id' => $share_id,
|
||||||
|
);
|
||||||
|
$res = curlHelper("https://api.aliyundrive.com/v2/share_link/get_share_token", "POST", json_encode($urlData), $this->urlHeader)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里-1-保存
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getAlipan3($urlData,$share_token)
|
||||||
|
{
|
||||||
|
$urlHeader= $this->urlHeader;
|
||||||
|
$urlHeader[] = 'X-Share-Token: '.$share_token;
|
||||||
|
$res = curlHelper("https://api.aliyundrive.com/adrive/v4/batch", "POST", json_encode($urlData), $urlHeader)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 阿里-2-分享
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getAlipan4($urlData)
|
||||||
|
{
|
||||||
|
$res = curlHelper("https://api.aliyundrive.com/adrive/v2/share_link/create", "POST", json_encode($urlData), $this->urlHeader)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理 access_token
|
||||||
|
* @param string $tokenFile token文件路径
|
||||||
|
* @return string access_token
|
||||||
|
*/
|
||||||
|
private function manageAccessToken($tokenFile)
|
||||||
|
{
|
||||||
|
$tokenData = [];
|
||||||
|
$currentRefreshToken = Config('qfshop.Authorization');
|
||||||
|
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (file_exists($tokenFile)) {
|
||||||
|
$tokenData = json_decode(file_get_contents($tokenFile), true);
|
||||||
|
|
||||||
|
// 检查token是否存在且未过期且不为空,且refresh_token未改变
|
||||||
|
if (isset($tokenData['access_token'])
|
||||||
|
&& isset($tokenData['expires_at'])
|
||||||
|
&& isset($tokenData['refresh_token'])
|
||||||
|
&& !empty($tokenData['access_token'])
|
||||||
|
&& time() < $tokenData['expires_at']
|
||||||
|
&& $tokenData['refresh_token'] === $currentRefreshToken
|
||||||
|
) {
|
||||||
|
return $tokenData['access_token'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取新的token
|
||||||
|
$newToken = $this->getNewAccessToken();
|
||||||
|
|
||||||
|
// 保存新token
|
||||||
|
$tokenData = [
|
||||||
|
'access_token' => $newToken,
|
||||||
|
'refresh_token' => $currentRefreshToken,
|
||||||
|
'expires_at' => time() + 3600
|
||||||
|
];
|
||||||
|
|
||||||
|
file_put_contents($tokenFile, json_encode($tokenData));
|
||||||
|
|
||||||
|
return $newToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取新的access_token
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getNewAccessToken()
|
||||||
|
{
|
||||||
|
$urlData = [
|
||||||
|
'refresh_token' => Config('qfshop.Authorization'),
|
||||||
|
];
|
||||||
|
$urlHeader = array(
|
||||||
|
'Content-Type: application/json',
|
||||||
|
);
|
||||||
|
$res = curlHelper("https://api.aliyundrive.com/token/refresh", "POST", json_encode($urlData),$urlHeader)['body'];
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
return $res['access_token']??'';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定资源
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deletepdirFid($filelist)
|
||||||
|
{
|
||||||
|
$urlData['requests'] = [];
|
||||||
|
$urlData['resource'] = 'file';
|
||||||
|
|
||||||
|
foreach ($filelist as $key=>$value) {
|
||||||
|
$urlData['requests'][$key]['body']['file_id'] = $value??'';
|
||||||
|
$urlData['requests'][$key]['body']['drive_id'] = '2008425230';
|
||||||
|
$urlData['requests'][$key]['headers']['Content-Type'] = 'application/json';
|
||||||
|
$urlData['requests'][$key]['id'] = $value??'';
|
||||||
|
$urlData['requests'][$key]['method'] = 'POST';
|
||||||
|
$urlData['requests'][$key]['url'] = '/recyclebin/trash';
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokenFile = __DIR__ . '/access_token.json';
|
||||||
|
$access_token = $this->manageAccessToken($tokenFile);
|
||||||
|
if(empty($access_token)){
|
||||||
|
return jerr2('登录状态异常,获取access_token失败');
|
||||||
|
}
|
||||||
|
foreach ($this->urlHeader as &$header) {
|
||||||
|
if (str_starts_with($header, 'Authorization: ')) {
|
||||||
|
$header = 'Authorization: Bearer ' . $access_token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$res = curlHelper("https://api.aliyundrive.com/adrive/v4/batch", "POST", json_encode($urlData), $this->urlHeader)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
252
demo/pan/BaiduPan.php
Normal file
252
demo/pan/BaiduPan.php
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
<?php
|
||||||
|
namespace netdisk\pan;
|
||||||
|
|
||||||
|
class BaiduPan extends BasePan
|
||||||
|
{
|
||||||
|
|
||||||
|
public function getFiles($pdir_fid=0)
|
||||||
|
{
|
||||||
|
if($pdir_fid === 0){
|
||||||
|
$pdir_fid = '/';
|
||||||
|
}
|
||||||
|
$cookie = Config('qfshop.baidu_cookie');
|
||||||
|
$network = new \netdisk\pan\BaiduWork($cookie);
|
||||||
|
|
||||||
|
$res = $network->getDirList($pdir_fid);
|
||||||
|
// 如果返回的是错误码,说明目录不存在,需要创建
|
||||||
|
if (is_numeric($res)) {
|
||||||
|
return jerr2($network->getErrorMessage($res));
|
||||||
|
}
|
||||||
|
|
||||||
|
return jok2('获取成功',$res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transfer($pwd_id)
|
||||||
|
{
|
||||||
|
$cookie = Config('qfshop.baidu_cookie');
|
||||||
|
$network = new \netdisk\pan\BaiduWork($cookie);
|
||||||
|
|
||||||
|
$bdstoken = $network->getBdstoken();
|
||||||
|
$network->setBdstoken($bdstoken);
|
||||||
|
|
||||||
|
$urlParts = parse_url($this->url);
|
||||||
|
$linkUrl = $urlParts['scheme'] . '://' . $urlParts['host'] . $urlParts['path']; // 分享链接
|
||||||
|
$passCode = $this->code; // 4位提取码
|
||||||
|
|
||||||
|
|
||||||
|
// 先判断是否有提取码
|
||||||
|
if (!empty($passCode)) {
|
||||||
|
// 验证提取码
|
||||||
|
$randsk = $network->verifyPassCode($linkUrl, $passCode);
|
||||||
|
if (is_numeric($randsk)) {
|
||||||
|
return jerr2($network->getErrorMessage($randsk));
|
||||||
|
}
|
||||||
|
// 验证成功,更新 cookie
|
||||||
|
$network->updateBdclnd($randsk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取转存参数
|
||||||
|
$transferParams = $network->getTransferParams($linkUrl);
|
||||||
|
if (is_numeric($transferParams)) {
|
||||||
|
return jerr2($network->getErrorMessage($transferParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析返回的参数
|
||||||
|
list($shareId, $userId, $fsIds, $fileNames, $isDirs) = $transferParams;
|
||||||
|
|
||||||
|
if($this->isType == 1){
|
||||||
|
$urls['title'] = $fileNames[0];
|
||||||
|
$urls['share_url'] = $this->url;
|
||||||
|
return jok2('检验成功', $urls);
|
||||||
|
}
|
||||||
|
|
||||||
|
$folderName = Config('qfshop.baidu_file'); //默认存储路径
|
||||||
|
if($this->expired_type == 2){
|
||||||
|
$folderName = Config('qfshop.baidu_file_time'); //临时资源路径
|
||||||
|
}
|
||||||
|
|
||||||
|
if(empty($folderName)){
|
||||||
|
$folderName = '/心悦转存文件'; // 未设置时默认目录
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查目录名是否包含非法字符
|
||||||
|
$invalidChars = ['<', '>', '|', '*', '?', '\\', ':'];
|
||||||
|
foreach ($invalidChars as $char) {
|
||||||
|
if (strpos($folderName, $char) !== false) {
|
||||||
|
return jerr2('转存目录名有非法字符,不能包含:< > | * ? \\ :');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先检查目录是否存在,不存在再创建
|
||||||
|
$dirList = $network->getDirList($folderName);
|
||||||
|
// 如果返回的是错误码,说明目录不存在,需要创建
|
||||||
|
if (is_numeric($dirList)) {
|
||||||
|
$createResult = $network->createDir($folderName);
|
||||||
|
if ($createResult !== 0) {
|
||||||
|
return jerr2($network->getErrorMessage($createResult));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行文件转存
|
||||||
|
$transferResult = $network->transferFile([$shareId, $userId, $fsIds], $folderName);
|
||||||
|
if ($transferResult !== 0) {
|
||||||
|
return jerr2($network->getErrorMessage($transferResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转存成功后,获取目录中的文件列表
|
||||||
|
$dirList = $network->getDirList('/' . $folderName);
|
||||||
|
if (is_numeric($dirList)) {
|
||||||
|
return jerr2($network->getErrorMessage($dirList));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到刚刚转存的所有文件
|
||||||
|
$targetFiles = [];
|
||||||
|
$fsIdList = [];
|
||||||
|
$filePaths = [];
|
||||||
|
$adFilePaths = []; // 用于存储包含广告的文件路径
|
||||||
|
$allFilesAreAds = true; // 假设所有文件都是广告
|
||||||
|
|
||||||
|
foreach ($dirList as $file) {
|
||||||
|
if (in_array($file['server_filename'], $fileNames)) {
|
||||||
|
$targetFiles[] = $file;
|
||||||
|
$fsIdList[] = $file['fs_id'];
|
||||||
|
$filePath = '/' . $folderName . '/' . $file['server_filename'];
|
||||||
|
$filePaths[] = $filePath;
|
||||||
|
|
||||||
|
// 检查文件名是否包含广告内容
|
||||||
|
$containsAd = $this->containsAdKeywords($file['server_filename']);
|
||||||
|
|
||||||
|
// 如果是目录,需要检查目录内的文件
|
||||||
|
if ($file['isdir'] == 1) {
|
||||||
|
// 获取子目录内容
|
||||||
|
$subDirList = $network->getDirList($filePath);
|
||||||
|
if (!is_numeric($subDirList)) {
|
||||||
|
foreach ($subDirList as $subFile) {
|
||||||
|
// 检查子文件是否包含广告关键词
|
||||||
|
if ($this->containsAdKeywords($subFile['server_filename'])) {
|
||||||
|
// 将包含广告的子文件添加到待删除列表
|
||||||
|
$adFilePaths[] = $filePath . '/' . $subFile['server_filename'];
|
||||||
|
} else {
|
||||||
|
// 只要有一个文件不是广告,就将标志设为false
|
||||||
|
$allFilesAreAds = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果是文件,直接判断
|
||||||
|
if ($containsAd) {
|
||||||
|
$adFilePaths[] = $filePath;
|
||||||
|
} else {
|
||||||
|
$allFilesAreAds = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($targetFiles)) {
|
||||||
|
return jerr2('分享失败,找不到刚转存的文件');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有文件都是广告,删除整个转存的内容
|
||||||
|
if ($allFilesAreAds && !empty($targetFiles)) {
|
||||||
|
// 删除所有转存的文件
|
||||||
|
$deleteResult = $network->batchDeleteFiles($filePaths);
|
||||||
|
if ($deleteResult['errno'] === 0) {
|
||||||
|
return jerr2('资源内容为空或所有转存的文件都包含广告内容,已全部删除');
|
||||||
|
} else {
|
||||||
|
// return jerr2('删除广告文件失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果只有部分文件是广告,只删除广告文件
|
||||||
|
if (!empty($adFilePaths)) {
|
||||||
|
$deleteResult = $network->batchDeleteFiles($adFilePaths);
|
||||||
|
// 删除后更新文件列表
|
||||||
|
if ($deleteResult['errno'] === 0) {
|
||||||
|
// 从fsIdList和filePaths中移除已删除的广告文件
|
||||||
|
foreach ($adFilePaths as $adPath) {
|
||||||
|
$key = array_search($adPath, $filePaths);
|
||||||
|
if ($key !== false) {
|
||||||
|
unset($filePaths[$key]);
|
||||||
|
unset($fsIdList[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 重新索引数组
|
||||||
|
$filePaths = array_values($filePaths);
|
||||||
|
$fsIdList = array_values($fsIdList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果删除广告后没有文件了,返回提示
|
||||||
|
if (empty($fsIdList)) {
|
||||||
|
return jerr2('资源内容为空或所有转存的文件都包含广告内容,已全部删除');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建分享
|
||||||
|
$expiry = 0; // 0为永久
|
||||||
|
// $password = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 4); // 随机4位提取码
|
||||||
|
$password = '6666'; // 随机4位提取码
|
||||||
|
$shareLink = $network->createShare(implode(',', $fsIdList), $expiry, $password);
|
||||||
|
|
||||||
|
if (is_numeric($shareLink)) {
|
||||||
|
return jerr2($network->getErrorMessage($shareLink));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!empty($password)){
|
||||||
|
$shareLink = $shareLink. '?pwd='. $password;
|
||||||
|
}
|
||||||
|
// 转存成功
|
||||||
|
return jok2("文件转存成功", [
|
||||||
|
'title' => $fileNames[0],
|
||||||
|
'share_url' => $shareLink,
|
||||||
|
'fid' => $filePaths,
|
||||||
|
'code' => $password,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查文件名是否包含广告关键词
|
||||||
|
* @param string $filename 文件名
|
||||||
|
* @return bool 是否包含广告关键词
|
||||||
|
*/
|
||||||
|
private function containsAdKeywords($filename)
|
||||||
|
{
|
||||||
|
$banned = Config('qfshop.quark_banned') ?? ''; // 如果出现这些字样就删除
|
||||||
|
|
||||||
|
// 广告关键词列表
|
||||||
|
$adKeywords = [];
|
||||||
|
if (!empty($banned)) {
|
||||||
|
$adKeywords = array_map('trim', explode(',', $banned));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转为小写进行比较
|
||||||
|
$lowercaseFilename = mb_strtolower($filename);
|
||||||
|
|
||||||
|
foreach ($adKeywords as $keyword) {
|
||||||
|
if (mb_strpos($lowercaseFilename, mb_strtolower($keyword)) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定资源
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deletepdirFid($filePaths)
|
||||||
|
{
|
||||||
|
$cookie = Config('qfshop.baidu_cookie');
|
||||||
|
$network = new \netdisk\pan\BaiduWork($cookie);
|
||||||
|
|
||||||
|
$bdstoken = $network->getBdstoken();
|
||||||
|
$network->setBdstoken($bdstoken);
|
||||||
|
|
||||||
|
// 调用批量删除方法
|
||||||
|
$result = $network->batchDeleteFiles($filePaths);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
416
demo/pan/BaiduWork.php
Normal file
416
demo/pan/BaiduWork.php
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
<?php
|
||||||
|
namespace netdisk\pan;
|
||||||
|
|
||||||
|
class BaiduWork
|
||||||
|
{
|
||||||
|
protected $errorCodes = [
|
||||||
|
-1 => '链接错误,链接失效或缺少提取码或访问频繁风控',
|
||||||
|
-4 => '无效登录。请退出账号在其他地方的登录',
|
||||||
|
-6 => '请用浏览器无痕模式获取 Cookie 后再试',
|
||||||
|
-7 => '转存失败,转存文件夹名有非法字符,不能包含 < > | * ? \\ :,请改正目录名后重试',
|
||||||
|
-8 => '转存失败,目录中已有同名文件或文件夹存在',
|
||||||
|
-9 => '链接不存在或提取码错误',
|
||||||
|
-10 => '转存失败,容量不足',
|
||||||
|
-12 => '链接错误,提取码错误',
|
||||||
|
-62 => '链接访问次数过多,请手动转存或稍后再试',
|
||||||
|
0 => '转存成功',
|
||||||
|
2 => '转存失败,目标目录不存在',
|
||||||
|
4 => '转存失败,目录中存在同名文件',
|
||||||
|
12 => '转存失败,转存文件数超过限制',
|
||||||
|
20 => '转存失败,容量不足',
|
||||||
|
105 => '链接错误,所访问的页面不存在',
|
||||||
|
115 => '该文件禁止分享'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function getErrorMessage($code)
|
||||||
|
{
|
||||||
|
return $this->errorCodes[$code] ?? "未知错误(错误码:{$code})";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $session;
|
||||||
|
protected $headers;
|
||||||
|
protected $bdstoken;
|
||||||
|
protected $baseUrl;
|
||||||
|
protected $cookie;
|
||||||
|
|
||||||
|
public function __construct($cookie = '')
|
||||||
|
{
|
||||||
|
$this->cookie = $cookie??'';
|
||||||
|
$this->headers = [
|
||||||
|
'Host: pan.baidu.com',
|
||||||
|
'Connection: keep-alive',
|
||||||
|
'Upgrade-Insecure-Requests: 1',
|
||||||
|
'Sec-Fetch-Dest: document',
|
||||||
|
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||||
|
'Sec-Fetch-Site: same-site',
|
||||||
|
'Sec-Fetch-Mode: navigate',
|
||||||
|
'Referer: https://pan.baidu.com',
|
||||||
|
'Accept-Encoding: gzip, deflate, br',
|
||||||
|
'Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7,en-GB;q=0.6,ru;q=0.5',
|
||||||
|
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
||||||
|
'Cookie: ' . $cookie
|
||||||
|
];
|
||||||
|
$this->bdstoken = '';
|
||||||
|
$this->baseUrl = 'https://pan.baidu.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBdstoken()
|
||||||
|
{
|
||||||
|
$url = $this->baseUrl . '/api/gettemplatevariable';
|
||||||
|
$params = [
|
||||||
|
'clienttype' => '0',
|
||||||
|
'app_id' => '38824127',
|
||||||
|
'web' => '1',
|
||||||
|
'fields' => '["bdstoken","token","uk","isdocuser","servertime"]'
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->request('GET', $url, $params);
|
||||||
|
if ($res['errno'] != 0) {
|
||||||
|
return $res['errno'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res['result']['bdstoken'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDirList($folderName)
|
||||||
|
{
|
||||||
|
$url = $this->baseUrl . '/api/list';
|
||||||
|
$params = [
|
||||||
|
'order' => 'time',
|
||||||
|
'desc' => '1',
|
||||||
|
'showempty' => '0',
|
||||||
|
'web' => '1',
|
||||||
|
'page' => '1',
|
||||||
|
'num' => '1000',
|
||||||
|
'dir' => $folderName,
|
||||||
|
'bdstoken' => $this->bdstoken
|
||||||
|
];
|
||||||
|
|
||||||
|
// print_r($params);
|
||||||
|
// die;
|
||||||
|
$res = $this->request('GET', $url, $params);
|
||||||
|
if ($res['errno'] != 0) {
|
||||||
|
return $res['errno'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res['list'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createDir($folderName)
|
||||||
|
{
|
||||||
|
$url = $this->baseUrl . '/api/create';
|
||||||
|
$params = [
|
||||||
|
'a' => 'commit',
|
||||||
|
'bdstoken' => $this->bdstoken
|
||||||
|
];
|
||||||
|
$data = [
|
||||||
|
'path' => $folderName,
|
||||||
|
'isdir' => '1',
|
||||||
|
'block_list' => '[]'
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->request('POST', $url, $params, $data);
|
||||||
|
return $res['errno'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verifyPassCode($linkUrl, $passCode)
|
||||||
|
{
|
||||||
|
$url = $this->baseUrl . '/share/verify';
|
||||||
|
$params = [
|
||||||
|
'surl' => substr($linkUrl, 25, 23),
|
||||||
|
'bdstoken' => $this->bdstoken,
|
||||||
|
't' => round(microtime(true) * 1000),
|
||||||
|
'channel' => 'chunlei',
|
||||||
|
'web' => '1',
|
||||||
|
'clienttype' => '0'
|
||||||
|
];
|
||||||
|
$data = [
|
||||||
|
'pwd' => $passCode,
|
||||||
|
'vcode' => '',
|
||||||
|
'vcode_str' => ''
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->request('POST', $url, $params, $data);
|
||||||
|
if ($res['errno'] != 0) {
|
||||||
|
return $res['errno'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res['randsk'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateBdclnd($randsk)
|
||||||
|
{
|
||||||
|
$this->cookie = $this->updateCookie($randsk, $this->cookie);
|
||||||
|
|
||||||
|
// 更新 headers 中的 cookie
|
||||||
|
foreach ($this->headers as &$header) {
|
||||||
|
if (strpos($header, 'Cookie:') === 0) {
|
||||||
|
$header = 'Cookie: ' . $this->cookie;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransferParams($url)
|
||||||
|
{
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||||
|
curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate');
|
||||||
|
// 允许跳转
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
|
curl_setopt($ch, CURLOPT_MAXREDIRS, 3);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new \Exception(curl_error($ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接返回原始响应内容,不做 json 解析
|
||||||
|
return $this->parseResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transferFile($paramsList, $folderName)
|
||||||
|
{
|
||||||
|
$url = $this->baseUrl . '/share/transfer';
|
||||||
|
$params = [
|
||||||
|
'shareid' => $paramsList[0],
|
||||||
|
'from' => $paramsList[1],
|
||||||
|
'bdstoken' => $this->bdstoken,
|
||||||
|
'channel' => 'chunlei',
|
||||||
|
'web' => '1',
|
||||||
|
'clienttype' => '0',
|
||||||
|
'ondup' => 'newcopy' //目录中存在同名文件无法转存的问题
|
||||||
|
];
|
||||||
|
$data = [
|
||||||
|
'fsidlist' => '[' . implode(',', $paramsList[2]) . ']',
|
||||||
|
'path' => '/' . $folderName
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->request('POST', $url, $params, $data);
|
||||||
|
return $res['errno'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createShare($fsId, $expiry, $password)
|
||||||
|
{
|
||||||
|
$url = $this->baseUrl . '/share/set';
|
||||||
|
$params = [
|
||||||
|
'channel' => 'chunlei',
|
||||||
|
'bdstoken' => $this->bdstoken,
|
||||||
|
'clienttype' => '0',
|
||||||
|
'app_id' => '250528',
|
||||||
|
'web' => '1'
|
||||||
|
];
|
||||||
|
$data = [
|
||||||
|
'period' => $expiry,
|
||||||
|
'pwd' => $password,
|
||||||
|
'eflag_disable' => 'true',
|
||||||
|
'channel_list' => '[]',
|
||||||
|
'schannel' => '4',
|
||||||
|
'fid_list' => '[' . $fsId . ']'
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->request('POST', $url, $params, $data);
|
||||||
|
if ($res['errno'] != 0) {
|
||||||
|
return $res['errno'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res['link'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBdstoken($token)
|
||||||
|
{
|
||||||
|
$this->bdstoken = $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function request($method, $url, $params = [], $data = null, $retry = 3)
|
||||||
|
{
|
||||||
|
while ($retry > 0) {
|
||||||
|
try {
|
||||||
|
$ch = curl_init();
|
||||||
|
if ($method === 'GET' && !empty($params)) {
|
||||||
|
$url .= '?' . http_build_query($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||||
|
curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate');
|
||||||
|
|
||||||
|
if ($method === 'POST') {
|
||||||
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
|
if (!empty($params)) {
|
||||||
|
$url .= '?' . http_build_query($params);
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
}
|
||||||
|
if (!empty($data)) {
|
||||||
|
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($response === false) {
|
||||||
|
throw new \Exception(curl_error($ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_decode($response, true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$retry--;
|
||||||
|
if ($retry <= 0) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
usleep(rand(1000000, 2000000)); // 1-2秒随机延迟
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function updateCookie($bdclnd, $cookie)
|
||||||
|
{
|
||||||
|
// 拆分 cookie 字符串到数组
|
||||||
|
$cookiePairs = array_filter(explode(';', $cookie));
|
||||||
|
$cookiesDict = [];
|
||||||
|
|
||||||
|
// 将 cookie 键值对转换为关联数组
|
||||||
|
foreach ($cookiePairs as $pair) {
|
||||||
|
$parts = explode('=', trim($pair), 2);
|
||||||
|
if (count($parts) == 2) {
|
||||||
|
$cookiesDict[$parts[0]] = $parts[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新或添加 BDCLND 值
|
||||||
|
$cookiesDict['BDCLND'] = $bdclnd;
|
||||||
|
|
||||||
|
// 重新构建 cookie 字符串
|
||||||
|
$cookieParts = [];
|
||||||
|
foreach ($cookiesDict as $key => $value) {
|
||||||
|
$cookieParts[] = $key . '=' . $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode('; ', $cookieParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseResponse($response)
|
||||||
|
{
|
||||||
|
// 预定义正则表达式
|
||||||
|
$patterns = [
|
||||||
|
'shareid' => '/"shareid":(\d+?),"/',
|
||||||
|
'user_id' => '/"share_uk":"(\d+?)","/',
|
||||||
|
'fs_id' => '/"fs_id":(\d+?),"/',
|
||||||
|
'server_filename' => '/"server_filename":"(.+?)","/',
|
||||||
|
'isdir' => '/"isdir":(\d+?),"/'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 提取所有需要的参数
|
||||||
|
$results = [];
|
||||||
|
foreach ($patterns as $key => $pattern) {
|
||||||
|
preg_match_all($pattern, $response, $matches);
|
||||||
|
$results[$key] = $matches[1] ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否获取到所有必要参数
|
||||||
|
if (empty($results['shareid']) || empty($results['user_id']) ||
|
||||||
|
empty($results['fs_id']) || empty($results['server_filename']) ||
|
||||||
|
empty($results['isdir'])) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回格式化的结果
|
||||||
|
return [
|
||||||
|
$results['shareid'][0], // shareid
|
||||||
|
$results['user_id'][0], // user_id
|
||||||
|
$results['fs_id'], // fs_id 列表
|
||||||
|
array_unique($results['server_filename']), // 文件名列表(去重)
|
||||||
|
$results['isdir'] // 是否为目录
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteFile($filePath)
|
||||||
|
{
|
||||||
|
$url = $this->baseUrl . '/api/filemanager';
|
||||||
|
$params = [
|
||||||
|
'async' => '2',
|
||||||
|
'onnest' => 'fail',
|
||||||
|
'opera' => 'delete',
|
||||||
|
'bdstoken' => $this->bdstoken,
|
||||||
|
'newVerify' => '1',
|
||||||
|
'clienttype' => '0',
|
||||||
|
'app_id' => '250528',
|
||||||
|
'web' => '1'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 支持单个路径字符串或路径数组
|
||||||
|
if (!is_array($filePath)) {
|
||||||
|
$filePath = [$filePath];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'filelist' => json_encode($filePath)
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = $this->request('POST', $url, $params, $data);
|
||||||
|
return $res['errno'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量删除文件
|
||||||
|
* @param array|string $filePaths 文件路径数组或单个文件路径
|
||||||
|
* @return array 删除结果
|
||||||
|
*/
|
||||||
|
public function batchDeleteFiles($filePaths)
|
||||||
|
{
|
||||||
|
// 如果是字符串,转换为数组处理
|
||||||
|
if (!is_array($filePaths)) {
|
||||||
|
$filePaths = [$filePaths];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理每个路径
|
||||||
|
$processedPaths = [];
|
||||||
|
foreach ($filePaths as $path) {
|
||||||
|
if (empty($path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止删除根目录
|
||||||
|
if ($path === '/' || $path === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保路径以斜杠开头
|
||||||
|
if (substr($path, 0, 1) !== '/') {
|
||||||
|
$path = '/' . $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
$processedPaths[] = $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($processedPaths)) {
|
||||||
|
return [
|
||||||
|
'errno' => -100,
|
||||||
|
'message' => '没有有效的文件路径可删除'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用删除方法
|
||||||
|
$result = $this->deleteFile($processedPaths);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'errno' => $result,
|
||||||
|
'message' => $this->getErrorMessage($result),
|
||||||
|
'deletedCount' => count($processedPaths),
|
||||||
|
'paths' => $processedPaths
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
demo/pan/BasePan.php
Normal file
25
demo/pan/BasePan.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace netdisk\pan;
|
||||||
|
|
||||||
|
abstract class BasePan
|
||||||
|
{
|
||||||
|
protected $urlHeader;
|
||||||
|
protected $code;
|
||||||
|
protected $url;
|
||||||
|
protected $isType;
|
||||||
|
protected $expired_type;
|
||||||
|
protected $ad_fid;
|
||||||
|
protected $stoken;
|
||||||
|
|
||||||
|
public function __construct($config = [])
|
||||||
|
{
|
||||||
|
$this->code = $config['code'] ?? '';
|
||||||
|
$this->url = $config['url'] ?? '';
|
||||||
|
$this->isType = $config['isType'] ?? 0;
|
||||||
|
$this->expired_type = $config['expired_type'] ?? 1;
|
||||||
|
$this->ad_fid = $config['ad_fid'] ?? '';
|
||||||
|
$this->stoken = $config['stoken'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function transfer($share_id);
|
||||||
|
}
|
||||||
449
demo/pan/QuarkPan.php
Normal file
449
demo/pan/QuarkPan.php
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
<?php
|
||||||
|
namespace netdisk\pan;
|
||||||
|
|
||||||
|
class QuarkPan extends BasePan
|
||||||
|
{
|
||||||
|
public function __construct($config = [])
|
||||||
|
{
|
||||||
|
parent::__construct($config);
|
||||||
|
$this->urlHeader = [
|
||||||
|
'Accept: application/json, text/plain, */*',
|
||||||
|
'Accept-Language: zh-CN,zh;q=0.9',
|
||||||
|
'content-type: application/json;charset=UTF-8',
|
||||||
|
'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',
|
||||||
|
'Referer: https://pan.quark.cn/',
|
||||||
|
'Referrer-Policy: strict-origin-when-cross-origin',
|
||||||
|
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
'cookie: ' . Config('qfshop.quark_cookie')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFiles($pdir_fid=0)
|
||||||
|
{
|
||||||
|
// 原 getFiles 方法内容
|
||||||
|
$urlData = [];
|
||||||
|
$queryParams = [
|
||||||
|
'pr' => 'ucpro',
|
||||||
|
'fr' => 'pc',
|
||||||
|
'uc_param_str' => '',
|
||||||
|
'pdir_fid' => $pdir_fid,
|
||||||
|
'_page' => 1,
|
||||||
|
'_size' => 50,
|
||||||
|
'_fetch_total' => 1,
|
||||||
|
'_fetch_sub_dirs' => 0,
|
||||||
|
'_sort' => 'file_type:asc,updated_at:desc',
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
if($res['status'] !== 200){
|
||||||
|
return jerr2($res['message']=='require login [guest]'?'夸克未登录,请检查cookie':$res['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jok2('获取成功',$res['data']['list']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transfer($pwd_id)
|
||||||
|
{
|
||||||
|
if(empty($this->stoken)){
|
||||||
|
//获取要转存夸克资源的stoken
|
||||||
|
$res = $this->getStoken($pwd_id);
|
||||||
|
if($res['status'] !== 200) return jerr2($res['message']);
|
||||||
|
$infoData = $res['data'];
|
||||||
|
|
||||||
|
if($this->isType == 1){
|
||||||
|
$urls['title'] = $infoData['title'];
|
||||||
|
$urls['share_url'] = $this->url;
|
||||||
|
$urls['stoken'] = $infoData['stoken'];
|
||||||
|
return jok2('检验成功', $urls);
|
||||||
|
}
|
||||||
|
$stoken = $infoData['stoken'];
|
||||||
|
$stoken = str_replace(' ', '+', $stoken);
|
||||||
|
}else{
|
||||||
|
$stoken = str_replace(' ', '+', $this->stoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取要转存夸克资源的详细内容
|
||||||
|
$res = $this->getShare($pwd_id,$stoken);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$detail = $res['data'];
|
||||||
|
|
||||||
|
$fid_list = [];
|
||||||
|
$fid_token_list = [];
|
||||||
|
$title = $detail['share']['title']; //资源名称
|
||||||
|
foreach ($detail['list'] as $key => $value) {
|
||||||
|
$fid_list[] = $value['fid'];
|
||||||
|
$fid_token_list[] = $value['share_fid_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
//转存资源到指定文件夹
|
||||||
|
$res = $this->getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$task_id = $res['data']['task_id'];
|
||||||
|
|
||||||
|
//转存后根据task_id获取转存到自己网盘后的信息
|
||||||
|
$retry_index = 0;
|
||||||
|
$myData = '';
|
||||||
|
while ($myData=='' || $myData['status'] != 2) {
|
||||||
|
$res = $this->getShareTask($task_id, $retry_index);
|
||||||
|
if($res['message']== 'capacity limit[{0}]'){
|
||||||
|
return jerr2('容量不足');
|
||||||
|
}
|
||||||
|
if($res['status']!== 200) {
|
||||||
|
return jerr2($res['message']);
|
||||||
|
}
|
||||||
|
$myData = $res['data'];
|
||||||
|
$retry_index++;
|
||||||
|
// 可以添加一个最大重试次数的限制,防止无限循环
|
||||||
|
if ($retry_index > 50) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//删除转存后可能有的广告
|
||||||
|
$banned = Config('qfshop.quark_banned')??''; //如果出现这些字样就删除
|
||||||
|
if(!empty($banned)){
|
||||||
|
$bannedList = explode(',', $banned);
|
||||||
|
$pdir_fid = $myData['save_as']['save_as_top_fids'][0];
|
||||||
|
$dellist = [];
|
||||||
|
$plist = $this->getPdirFid($pdir_fid);
|
||||||
|
if(!empty($plist)){
|
||||||
|
foreach ($plist as $key => $value) {
|
||||||
|
// 检查$value['file_name']是否包含$bannedList中的任何一项
|
||||||
|
$contains = false;
|
||||||
|
foreach ($bannedList as $item) {
|
||||||
|
if (strpos($value['file_name'], $item) !== false) {
|
||||||
|
$contains = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($contains) {
|
||||||
|
$dellist[] = $value['fid'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(count($plist) === count($dellist)){
|
||||||
|
//要删除的资源数如果和原数据资源数一样 就全部删除并终止下面的分享
|
||||||
|
$this->deletepdirFid([$pdir_fid]);
|
||||||
|
return jerr2("资源内容为空");
|
||||||
|
}else{
|
||||||
|
if (!empty($dellist)) {
|
||||||
|
$this->deletepdirFid($dellist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
$shareFid = $myData['save_as']['save_as_top_fids'];
|
||||||
|
//分享资源并拿到更新后的task_id
|
||||||
|
$res = $this->getShareBtn($myData['save_as']['save_as_top_fids'],$title);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$task_id = $res['data']['task_id'];
|
||||||
|
|
||||||
|
//根据task_id拿到share_id
|
||||||
|
$retry_index = 0;
|
||||||
|
$myData = '';
|
||||||
|
while ($myData=='' || $myData['status'] != 2) {
|
||||||
|
$res = $this->getShareTask($task_id, $retry_index);
|
||||||
|
if($res['status']!== 200) continue;
|
||||||
|
$myData = $res['data'];
|
||||||
|
$retry_index++;
|
||||||
|
// 可以添加一个最大重试次数的限制,防止无限循环
|
||||||
|
if ($retry_index > 50) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//根据share_id 获取到分享链接
|
||||||
|
$res = $this->getSharePassword($myData['share_id']);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$share = $res['data'];
|
||||||
|
// $share['fid'] = $share['first_file']['fid'];
|
||||||
|
$share['fid'] = (is_array($shareFid) && count($shareFid) > 1) ? $shareFid : $share['first_file']['fid'];
|
||||||
|
|
||||||
|
return jok2('转存成功', $share);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取要转存资源的stoken
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getStoken($pwd_id)
|
||||||
|
{
|
||||||
|
$urlData = array(
|
||||||
|
'passcode' => '',
|
||||||
|
'pwd_id' => $pwd_id,
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
'pr' => 'ucpro',
|
||||||
|
'fr' => 'pc',
|
||||||
|
'uc_param_str' => '',
|
||||||
|
];
|
||||||
|
return $this->executeApiRequest(
|
||||||
|
"https://drive-pc.quark.cn/1/clouddrive/share/sharepage/token",
|
||||||
|
"POST",
|
||||||
|
$urlData,
|
||||||
|
$queryParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取要转存资源的详细内容
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
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",
|
||||||
|
$urlData,
|
||||||
|
$queryParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转存资源到指定文件夹
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list)
|
||||||
|
{
|
||||||
|
if(!empty($this->to_pdir_fid)){
|
||||||
|
$to_pdir_fid = $this->to_pdir_fid;
|
||||||
|
}else{
|
||||||
|
$to_pdir_fid = Config('qfshop.quark_file'); //默认存储路径
|
||||||
|
if($this->expired_type == 2){
|
||||||
|
$to_pdir_fid = Config('qfshop.quark_file_time'); //临时资源路径
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$urlData = array(
|
||||||
|
'fid_list' => $fid_list,
|
||||||
|
'fid_token_list' => $fid_token_list,
|
||||||
|
'to_pdir_fid' => $to_pdir_fid,
|
||||||
|
'pwd_id' => $pwd_id,
|
||||||
|
'stoken' => $stoken,
|
||||||
|
'pdir_fid' => "0",
|
||||||
|
'scene' => "link",
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"entry" => "update_share",
|
||||||
|
"pr" => "ucpro",
|
||||||
|
"fr" => "pc",
|
||||||
|
"uc_param_str" => ""
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->executeApiRequest(
|
||||||
|
"https://drive-pc.quark.cn/1/clouddrive/share/sharepage/save",
|
||||||
|
"POST",
|
||||||
|
$urlData,
|
||||||
|
$queryParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享资源拿到task_id
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getShareBtn($fid_list,$title)
|
||||||
|
{
|
||||||
|
if(!empty($this->ad_fid)){
|
||||||
|
$fid_list[] = $this->ad_fid;
|
||||||
|
}
|
||||||
|
$urlData = array(
|
||||||
|
'fid_list' => $fid_list,
|
||||||
|
'expired_type' => $this->expired_type,
|
||||||
|
'title' => $title,
|
||||||
|
'url_type' => 1,
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "ucpro",
|
||||||
|
"fr" => "pc",
|
||||||
|
"uc_param_str" => ""
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->executeApiRequest(
|
||||||
|
"https://drive-pc.quark.cn/1/clouddrive/share",
|
||||||
|
"POST",
|
||||||
|
$urlData,
|
||||||
|
$queryParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据task_id拿到自己的资源信息
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getShareTask($task_id,$retry_index)
|
||||||
|
{
|
||||||
|
$urlData = array();
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "ucpro",
|
||||||
|
"fr" => "pc",
|
||||||
|
"uc_param_str" => "",
|
||||||
|
"task_id" => $task_id,
|
||||||
|
"retry_index" => $retry_index
|
||||||
|
];
|
||||||
|
return $this->executeApiRequest(
|
||||||
|
"https://drive-pc.quark.cn/1/clouddrive/task",
|
||||||
|
"GET",
|
||||||
|
$urlData,
|
||||||
|
$queryParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据share_id 获取到分享链接
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getSharePassword($share_id)
|
||||||
|
{
|
||||||
|
$urlData = array(
|
||||||
|
'share_id' => $share_id,
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "ucpro",
|
||||||
|
"fr" => "pc",
|
||||||
|
"uc_param_str" => ""
|
||||||
|
];
|
||||||
|
return $this->executeApiRequest(
|
||||||
|
"https://drive-pc.quark.cn/1/clouddrive/share/password",
|
||||||
|
"POST",
|
||||||
|
$urlData,
|
||||||
|
$queryParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定资源
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deletepdirFid($filelist)
|
||||||
|
{
|
||||||
|
$urlData = array(
|
||||||
|
'action_type' => 2,
|
||||||
|
'exclude_fids' => [],
|
||||||
|
'filelist' => $filelist,
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "ucpro",
|
||||||
|
"fr" => "pc",
|
||||||
|
"uc_param_str" => ""
|
||||||
|
];
|
||||||
|
return $this->executeApiRequest(
|
||||||
|
"https://drive-pc.quark.cn/1/clouddrive/file/delete",
|
||||||
|
"POST",
|
||||||
|
$urlData,
|
||||||
|
$queryParams
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取夸克网盘指定文件夹内容
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getPdirFid($pdir_fid)
|
||||||
|
{
|
||||||
|
$urlData = [];
|
||||||
|
$queryParams = [
|
||||||
|
'pr' => 'ucpro',
|
||||||
|
'fr' => 'pc',
|
||||||
|
'uc_param_str' => '',
|
||||||
|
'pdir_fid' => $pdir_fid,
|
||||||
|
'_page' => 1,
|
||||||
|
'_size' => 200,
|
||||||
|
'_fetch_total' => 1,
|
||||||
|
'_fetch_sub_dirs' => 0,
|
||||||
|
'_sort' => 'file_type:asc,updated_at:desc',
|
||||||
|
];
|
||||||
|
try {
|
||||||
|
$res = curlHelper("https://drive-pc.quark.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
if($res['status'] !== 200){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $res['data']['list'];
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行API请求并处理重试逻辑
|
||||||
|
*
|
||||||
|
* @param string $url 请求URL
|
||||||
|
* @param string $method 请求方法(GET/POST)
|
||||||
|
* @param array $data 请求数据
|
||||||
|
* @param array $queryParams 查询参数
|
||||||
|
* @param int $maxRetries 最大重试次数
|
||||||
|
* @param int $retryDelay 重试延迟(秒)
|
||||||
|
* @return array 响应结果
|
||||||
|
*/
|
||||||
|
protected function executeApiRequest($url, $method, $data = [], $queryParams = [], $maxRetries = 3, $retryDelay = 2)
|
||||||
|
{
|
||||||
|
$attempt = 0;
|
||||||
|
while ($attempt < $maxRetries) {
|
||||||
|
$attempt++;
|
||||||
|
try {
|
||||||
|
$res = curlHelper($url, $method, json_encode($data), $this->urlHeader, $queryParams)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->logApiError($url, $attempt, $e->getMessage());
|
||||||
|
if ($attempt < $maxRetries) {
|
||||||
|
sleep($retryDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['status' => 500, 'message' => '接口请求异常'];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 记录API错误日志
|
||||||
|
*
|
||||||
|
* @param string $prefix 日志前缀
|
||||||
|
* @param int $attempt 尝试次数
|
||||||
|
* @param mixed $error 错误信息
|
||||||
|
*/
|
||||||
|
protected function logApiError($prefix, $attempt, $error)
|
||||||
|
{
|
||||||
|
$errorMsg = is_scalar($error) ? $error : json_encode($error);
|
||||||
|
$logMessage = date('Y-m-d H:i:s') . ' ' . $prefix . '请求失败(尝试次数: ' . $attempt . ') 错误: ' . $errorMsg . "\n";
|
||||||
|
file_put_contents('error.log', $logMessage, FILE_APPEND);
|
||||||
|
}
|
||||||
|
}
|
||||||
348
demo/pan/UcPan.php
Normal file
348
demo/pan/UcPan.php
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
<?php
|
||||||
|
namespace netdisk\pan;
|
||||||
|
|
||||||
|
class UcPan extends BasePan
|
||||||
|
{
|
||||||
|
public function __construct($config = [])
|
||||||
|
{
|
||||||
|
parent::__construct($config);
|
||||||
|
$this->urlHeader = [
|
||||||
|
'Accept: application/json, text/plain, */*',
|
||||||
|
'Accept-Language: zh-CN,zh;q=0.9',
|
||||||
|
'content-type: application/json;charset=UTF-8',
|
||||||
|
'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',
|
||||||
|
'Referer: https://drive.uc.cn/',
|
||||||
|
'Referrer-Policy: strict-origin-when-cross-origin',
|
||||||
|
'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
|
'cookie: ' . Config('qfshop.uc_cookie')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFiles($pdir_fid=0)
|
||||||
|
{
|
||||||
|
// 原 getFiles 方法内容
|
||||||
|
$urlData = [];
|
||||||
|
$queryParams = [
|
||||||
|
'pr' => 'UCBrowser',
|
||||||
|
'fr' => 'pc',
|
||||||
|
'pdir_fid' => $pdir_fid,
|
||||||
|
'_page' => 1,
|
||||||
|
'_size' => 50,
|
||||||
|
'_fetch_total' => 1,
|
||||||
|
'_fetch_sub_dirs' => 0,
|
||||||
|
'_sort' => 'file_type:asc,updated_at:desc',
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
if($res['status'] !== 200){
|
||||||
|
return jerr2($res['message']=='require login [guest]'?'UC未登录,请检查cookie':$res['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jok2('获取成功',$res['data']['list']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function transfer($pwd_id)
|
||||||
|
{
|
||||||
|
//获取要转存UC资源的stoken
|
||||||
|
$res = $this->getStoken($pwd_id);
|
||||||
|
if($res['status'] !== 200) return jerr2($res['message']);
|
||||||
|
$infoData = $res['data'];
|
||||||
|
|
||||||
|
if($this->isType == 1){
|
||||||
|
$urls['title'] = $infoData['token_info']['title'];
|
||||||
|
$urls['share_url'] = $this->url;
|
||||||
|
return jok2('检验成功', $urls);
|
||||||
|
}
|
||||||
|
$stoken = $infoData['token_info']['stoken'];
|
||||||
|
$stoken = str_replace(' ', '+', $stoken);
|
||||||
|
|
||||||
|
//获取要转存UC资源的详细内容
|
||||||
|
$res = $this->getShare($pwd_id,$stoken);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$detail = $res['data'];
|
||||||
|
|
||||||
|
$fid_list = [];
|
||||||
|
$fid_token_list = [];
|
||||||
|
$title = $detail['share']['title']; //资源名称
|
||||||
|
foreach ($detail['list'] as $key => $value) {
|
||||||
|
$fid_list[] = $value['fid'];
|
||||||
|
$fid_token_list[] = $value['share_fid_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
//转存资源到指定文件夹
|
||||||
|
$res = $this->getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$task_id = $res['data']['task_id'];
|
||||||
|
|
||||||
|
//转存后根据task_id获取转存到自己网盘后的信息
|
||||||
|
$retry_index = 0;
|
||||||
|
$myData = '';
|
||||||
|
while ($myData=='' || $myData['status'] != 2) {
|
||||||
|
$res = $this->getShareTask($task_id, $retry_index);
|
||||||
|
if($res['message']== 'capacity limit[{0}]'){
|
||||||
|
return jerr2('容量不足');
|
||||||
|
}
|
||||||
|
if($res['status']!== 200) {
|
||||||
|
return jerr2($res['message']);
|
||||||
|
}
|
||||||
|
$myData = $res['data'];
|
||||||
|
$retry_index++;
|
||||||
|
// 可以添加一个最大重试次数的限制,防止无限循环
|
||||||
|
if ($retry_index > 50) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
//删除转存后可能有的广告
|
||||||
|
$banned = Config('qfshop.quark_banned')??''; //如果出现这些字样就删除
|
||||||
|
if(!empty($banned)){
|
||||||
|
$bannedList = explode(',', $banned);
|
||||||
|
$pdir_fid = $myData['save_as']['save_as_top_fids'][0];
|
||||||
|
$dellist = [];
|
||||||
|
$plist = $this->getPdirFid($pdir_fid);
|
||||||
|
if(!empty($plist)){
|
||||||
|
foreach ($plist as $key => $value) {
|
||||||
|
// 检查$value['file_name']是否包含$bannedList中的任何一项
|
||||||
|
$contains = false;
|
||||||
|
foreach ($bannedList as $item) {
|
||||||
|
if (strpos($value['file_name'], $item) !== false) {
|
||||||
|
$contains = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($contains) {
|
||||||
|
$dellist[] = $value['fid'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(count($plist) === count($dellist)){
|
||||||
|
//要删除的资源数如果和原数据资源数一样 就全部删除并终止下面的分享
|
||||||
|
$this->deletepdirFid([$pdir_fid]);
|
||||||
|
return jerr2("资源内容为空");
|
||||||
|
}else{
|
||||||
|
if (!empty($dellist)) {
|
||||||
|
$this->deletepdirFid($dellist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
$shareFid = $myData['save_as']['save_as_top_fids'];
|
||||||
|
//分享资源并拿到更新后的task_id
|
||||||
|
$res = $this->getShareBtn($myData['save_as']['save_as_top_fids'],$title);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$task_id = $res['data']['task_id'];
|
||||||
|
|
||||||
|
//根据task_id拿到share_id
|
||||||
|
$retry_index = 0;
|
||||||
|
$myData = '';
|
||||||
|
while ($myData=='' || $myData['status'] != 2) {
|
||||||
|
$res = $this->getShareTask($task_id, $retry_index);
|
||||||
|
if($res['status']!== 200) continue;
|
||||||
|
$myData = $res['data'];
|
||||||
|
$retry_index++;
|
||||||
|
// 可以添加一个最大重试次数的限制,防止无限循环
|
||||||
|
if ($retry_index > 50) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//根据share_id 获取到分享链接
|
||||||
|
$res = $this->getSharePassword($myData['share_id']);
|
||||||
|
if($res['status']!== 200) return jerr2($res['message']);
|
||||||
|
$share = $res['data'];
|
||||||
|
// $share['fid'] = $share['first_file']['fid'];
|
||||||
|
$share['fid'] = (is_array($shareFid) && count($shareFid) > 1) ? $shareFid : $share['first_file']['fid'];
|
||||||
|
|
||||||
|
return jok2('转存成功', $share);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取要转存资源的stoken
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getStoken($pwd_id)
|
||||||
|
{
|
||||||
|
$urlData = array(
|
||||||
|
'passcode' => '',
|
||||||
|
'pwd_id' => $pwd_id,
|
||||||
|
);
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/sharepage/v2/detail?pr=UCBrowser&fr=pc", "POST",json_encode($urlData), $this->urlHeader)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取要转存资源的详细内容
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getShare($pwd_id,$stoken)
|
||||||
|
{
|
||||||
|
$urlData = array();
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "UCBrowser",
|
||||||
|
"fr" => "pc",
|
||||||
|
"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"
|
||||||
|
];
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/sharepage/detail", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转存资源到指定文件夹
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getShareSave($pwd_id,$stoken,$fid_list,$fid_token_list)
|
||||||
|
{
|
||||||
|
$to_pdir_fid = Config('qfshop.uc_file'); //默认存储路径
|
||||||
|
if($this->expired_type == 2){
|
||||||
|
$to_pdir_fid = Config('qfshop.uc_file_time'); //临时资源路径
|
||||||
|
}
|
||||||
|
$urlData = array(
|
||||||
|
'fid_list' => $fid_list,
|
||||||
|
'fid_token_list' => $fid_token_list,
|
||||||
|
'to_pdir_fid' => $to_pdir_fid,
|
||||||
|
'pwd_id' => $pwd_id,
|
||||||
|
'stoken' => $stoken,
|
||||||
|
'pdir_fid' => "0",
|
||||||
|
'scene' => "link",
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"entry" => "update_share",
|
||||||
|
"pr" => "UCBrowser",
|
||||||
|
"fr" => "pc",
|
||||||
|
];
|
||||||
|
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/sharepage/save", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享资源拿到task_id
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getShareBtn($fid_list,$title)
|
||||||
|
{
|
||||||
|
if(!empty($this->ad_fid)){
|
||||||
|
$fid_list[] = $this->ad_fid;
|
||||||
|
}
|
||||||
|
$urlData = array(
|
||||||
|
'fid_list' => $fid_list,
|
||||||
|
'expired_type' => $this->expired_type,
|
||||||
|
'title' => $title,
|
||||||
|
'url_type' => 1,
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "UCBrowser",
|
||||||
|
"fr" => "pc",
|
||||||
|
];
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据task_id拿到自己的资源信息
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getShareTask($task_id,$retry_index)
|
||||||
|
{
|
||||||
|
$urlData = array();
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "UCBrowser",
|
||||||
|
"fr" => "pc",
|
||||||
|
"task_id" => $task_id,
|
||||||
|
"retry_index" => $retry_index
|
||||||
|
];
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/task", "GET", json_encode($urlData), $this->urlHeader, $queryParams)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据share_id 获取到分享链接
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getSharePassword($share_id)
|
||||||
|
{
|
||||||
|
$urlData = array(
|
||||||
|
'share_id' => $share_id,
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "UCBrowser",
|
||||||
|
"fr" => "pc",
|
||||||
|
];
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/share/password", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
return json_decode($res, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除指定资源
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deletepdirFid($filelist)
|
||||||
|
{
|
||||||
|
$urlData = array(
|
||||||
|
'action_type' => 2,
|
||||||
|
'exclude_fids' => [],
|
||||||
|
'filelist' => $filelist,
|
||||||
|
);
|
||||||
|
$queryParams = [
|
||||||
|
"pr" => "UCBrowser",
|
||||||
|
"fr" => "pc",
|
||||||
|
];
|
||||||
|
curlHelper("https://pc-api.uc.cn/1/clouddrive/file/delete", "POST", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取夸克网盘指定文件夹内容
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function getPdirFid($pdir_fid)
|
||||||
|
{
|
||||||
|
$urlData = [];
|
||||||
|
$queryParams = [
|
||||||
|
'pr' => 'UCBrowser',
|
||||||
|
'fr' => 'pc',
|
||||||
|
'pdir_fid' => $pdir_fid,
|
||||||
|
'_page' => 1,
|
||||||
|
'_size' => 200,
|
||||||
|
'_fetch_total' => 1,
|
||||||
|
'_fetch_sub_dirs' => 0,
|
||||||
|
'_sort' => 'file_type:asc,updated_at:desc',
|
||||||
|
];
|
||||||
|
$res = curlHelper("https://pc-api.uc.cn/1/clouddrive/file/sort", "GET", json_encode($urlData), $this->urlHeader,$queryParams)['body'];
|
||||||
|
$res = json_decode($res, true);
|
||||||
|
if($res['status'] !== 200){
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $res['data']['list'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ func GetResources(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetResourceByID 根据ID获取资源详情
|
// GetResourceByID 根据ID获取资源
|
||||||
func GetResourceByID(c *gin.Context) {
|
func GetResourceByID(c *gin.Context) {
|
||||||
idStr := c.Param("id")
|
idStr := c.Param("id")
|
||||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||||
@@ -63,15 +63,38 @@ func GetResourceByID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 增加浏览次数
|
|
||||||
if resource != nil {
|
|
||||||
repoManager.ResourceRepository.IncrementViewCount(uint(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
response := converter.ToResourceResponse(resource)
|
response := converter.ToResourceResponse(resource)
|
||||||
SuccessResponse(c, response)
|
SuccessResponse(c, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckResourceExists 检查资源是否存在(测试FindExists函数)
|
||||||
|
func CheckResourceExists(c *gin.Context) {
|
||||||
|
url := c.Query("url")
|
||||||
|
if url == "" {
|
||||||
|
ErrorResponse(c, "URL参数不能为空", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
excludeIDStr := c.Query("exclude_id")
|
||||||
|
var excludeID uint
|
||||||
|
if excludeIDStr != "" {
|
||||||
|
if id, err := strconv.ParseUint(excludeIDStr, 10, 32); err == nil {
|
||||||
|
excludeID = uint(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := repoManager.ResourceRepository.FindExists(url, excludeID)
|
||||||
|
if err != nil {
|
||||||
|
ErrorResponse(c, "检查失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
SuccessResponse(c, gin.H{
|
||||||
|
"url": url,
|
||||||
|
"exists": exists,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// CreateResource 创建资源
|
// CreateResource 创建资源
|
||||||
func CreateResource(c *gin.Context) {
|
func CreateResource(c *gin.Context) {
|
||||||
var req dto.CreateResourceRequest
|
var req dto.CreateResourceRequest
|
||||||
|
|||||||
@@ -9,10 +9,16 @@ import (
|
|||||||
|
|
||||||
// GetSchedulerStatus 获取调度器状态
|
// GetSchedulerStatus 获取调度器状态
|
||||||
func GetSchedulerStatus(c *gin.Context) {
|
func GetSchedulerStatus(c *gin.Context) {
|
||||||
scheduler := utils.GetGlobalScheduler(repoManager.HotDramaRepository)
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
|
|
||||||
status := gin.H{
|
status := gin.H{
|
||||||
"hot_drama_scheduler_running": scheduler.IsHotDramaSchedulerRunning(),
|
"hot_drama_scheduler_running": scheduler.IsHotDramaSchedulerRunning(),
|
||||||
|
"ready_resource_scheduler_running": scheduler.IsReadyResourceRunning(),
|
||||||
}
|
}
|
||||||
|
|
||||||
SuccessResponse(c, status)
|
SuccessResponse(c, status)
|
||||||
@@ -20,7 +26,12 @@ func GetSchedulerStatus(c *gin.Context) {
|
|||||||
|
|
||||||
// 启动热播剧定时任务
|
// 启动热播剧定时任务
|
||||||
func StartHotDramaScheduler(c *gin.Context) {
|
func StartHotDramaScheduler(c *gin.Context) {
|
||||||
scheduler := utils.GetGlobalScheduler(repoManager.HotDramaRepository)
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
if scheduler.IsHotDramaSchedulerRunning() {
|
if scheduler.IsHotDramaSchedulerRunning() {
|
||||||
ErrorResponse(c, "热播剧定时任务已在运行中", http.StatusBadRequest)
|
ErrorResponse(c, "热播剧定时任务已在运行中", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -31,7 +42,12 @@ func StartHotDramaScheduler(c *gin.Context) {
|
|||||||
|
|
||||||
// 停止热播剧定时任务
|
// 停止热播剧定时任务
|
||||||
func StopHotDramaScheduler(c *gin.Context) {
|
func StopHotDramaScheduler(c *gin.Context) {
|
||||||
scheduler := utils.GetGlobalScheduler(repoManager.HotDramaRepository)
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
if !scheduler.IsHotDramaSchedulerRunning() {
|
if !scheduler.IsHotDramaSchedulerRunning() {
|
||||||
ErrorResponse(c, "热播剧定时任务未在运行", http.StatusBadRequest)
|
ErrorResponse(c, "热播剧定时任务未在运行", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
@@ -42,14 +58,24 @@ func StopHotDramaScheduler(c *gin.Context) {
|
|||||||
|
|
||||||
// 手动触发热播剧定时任务
|
// 手动触发热播剧定时任务
|
||||||
func TriggerHotDramaScheduler(c *gin.Context) {
|
func TriggerHotDramaScheduler(c *gin.Context) {
|
||||||
scheduler := utils.GetGlobalScheduler(repoManager.HotDramaRepository)
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
scheduler.StartHotDramaScheduler() // 直接启动一次
|
scheduler.StartHotDramaScheduler() // 直接启动一次
|
||||||
SuccessResponse(c, gin.H{"message": "手动触发热播剧定时任务成功"})
|
SuccessResponse(c, gin.H{"message": "手动触发热播剧定时任务成功"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手动获取热播剧名字
|
// 手动获取热播剧名字
|
||||||
func FetchHotDramaNames(c *gin.Context) {
|
func FetchHotDramaNames(c *gin.Context) {
|
||||||
scheduler := utils.GetGlobalScheduler(repoManager.HotDramaRepository)
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
names, err := scheduler.GetHotDramaNames()
|
names, err := scheduler.GetHotDramaNames()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorResponse(c, "获取热播剧名字失败: "+err.Error(), http.StatusInternalServerError)
|
ErrorResponse(c, "获取热播剧名字失败: "+err.Error(), http.StatusInternalServerError)
|
||||||
@@ -57,3 +83,48 @@ func FetchHotDramaNames(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
SuccessResponse(c, gin.H{"names": names})
|
SuccessResponse(c, gin.H{"names": names})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 启动待处理资源自动处理任务
|
||||||
|
func StartReadyResourceScheduler(c *gin.Context) {
|
||||||
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
|
if scheduler.IsReadyResourceRunning() {
|
||||||
|
ErrorResponse(c, "待处理资源自动处理任务已在运行中", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scheduler.StartReadyResourceScheduler()
|
||||||
|
SuccessResponse(c, gin.H{"message": "待处理资源自动处理任务已启动"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止待处理资源自动处理任务
|
||||||
|
func StopReadyResourceScheduler(c *gin.Context) {
|
||||||
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
|
if !scheduler.IsReadyResourceRunning() {
|
||||||
|
ErrorResponse(c, "待处理资源自动处理任务未在运行", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scheduler.StopReadyResourceScheduler()
|
||||||
|
SuccessResponse(c, gin.H{"message": "待处理资源自动处理任务已停止"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动触发待处理资源自动处理任务
|
||||||
|
func TriggerReadyResourceScheduler(c *gin.Context) {
|
||||||
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
|
// 手动触发一次处理
|
||||||
|
scheduler.ProcessReadyResources()
|
||||||
|
SuccessResponse(c, gin.H{"message": "手动触发待处理资源自动处理任务成功"})
|
||||||
|
}
|
||||||
|
|||||||
@@ -134,7 +134,12 @@ func UpdateSystemConfig(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 根据配置更新定时任务状态(错误不影响配置保存)
|
// 根据配置更新定时任务状态(错误不影响配置保存)
|
||||||
scheduler := utils.GetGlobalScheduler(repoManager.HotDramaRepository)
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
if scheduler != nil {
|
if scheduler != nil {
|
||||||
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled)
|
scheduler.UpdateSchedulerStatus(req.AutoFetchHotDramaEnabled)
|
||||||
}
|
}
|
||||||
|
|||||||
37
main.go
37
main.go
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"res_db/utils"
|
||||||
|
|
||||||
"res_db/db"
|
"res_db/db"
|
||||||
"res_db/db/repo"
|
"res_db/db/repo"
|
||||||
@@ -28,6 +29,36 @@ func main() {
|
|||||||
// 创建Repository管理器
|
// 创建Repository管理器
|
||||||
repoManager := repo.NewRepositoryManager(db.DB)
|
repoManager := repo.NewRepositoryManager(db.DB)
|
||||||
|
|
||||||
|
// 创建全局调度器
|
||||||
|
scheduler := utils.GetGlobalScheduler(
|
||||||
|
repoManager.HotDramaRepository,
|
||||||
|
repoManager.ReadyResourceRepository,
|
||||||
|
repoManager.ResourceRepository,
|
||||||
|
repoManager.SystemConfigRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 检查系统配置,决定是否启动待处理资源自动处理任务
|
||||||
|
systemConfig, err := repoManager.SystemConfigRepository.GetOrCreateDefault()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取系统配置失败: %v", err)
|
||||||
|
} else {
|
||||||
|
// 检查是否启动待处理资源自动处理任务
|
||||||
|
if systemConfig.AutoProcessReadyResources {
|
||||||
|
scheduler.StartReadyResourceScheduler()
|
||||||
|
log.Println("已启动待处理资源自动处理任务")
|
||||||
|
} else {
|
||||||
|
log.Println("系统配置中自动处理待处理资源功能已禁用,跳过启动定时任务")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否启动热播剧自动拉取任务
|
||||||
|
if systemConfig.AutoFetchHotDramaEnabled {
|
||||||
|
scheduler.StartHotDramaScheduler()
|
||||||
|
log.Println("已启动热播剧自动拉取任务")
|
||||||
|
} else {
|
||||||
|
log.Println("系统配置中自动拉取热播剧功能已禁用,跳过启动定时任务")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 创建Gin实例
|
// 创建Gin实例
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
@@ -55,6 +86,7 @@ func main() {
|
|||||||
api.PUT("/resources/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.UpdateResource)
|
api.PUT("/resources/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.UpdateResource)
|
||||||
api.DELETE("/resources/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.DeleteResource)
|
api.DELETE("/resources/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.DeleteResource)
|
||||||
api.GET("/resources/:id", handlers.GetResourceByID)
|
api.GET("/resources/:id", handlers.GetResourceByID)
|
||||||
|
api.GET("/resources/check-exists", handlers.CheckResourceExists)
|
||||||
|
|
||||||
// 分类管理
|
// 分类管理
|
||||||
api.GET("/categories", handlers.GetCategories)
|
api.GET("/categories", handlers.GetCategories)
|
||||||
@@ -130,6 +162,11 @@ func main() {
|
|||||||
api.POST("/scheduler/hot-drama/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StartHotDramaScheduler)
|
api.POST("/scheduler/hot-drama/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StartHotDramaScheduler)
|
||||||
api.POST("/scheduler/hot-drama/stop", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StopHotDramaScheduler)
|
api.POST("/scheduler/hot-drama/stop", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StopHotDramaScheduler)
|
||||||
api.POST("/scheduler/hot-drama/trigger", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.TriggerHotDramaScheduler)
|
api.POST("/scheduler/hot-drama/trigger", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.TriggerHotDramaScheduler)
|
||||||
|
|
||||||
|
// 待处理资源自动处理管理路由
|
||||||
|
api.POST("/scheduler/ready-resource/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StartReadyResourceScheduler)
|
||||||
|
api.POST("/scheduler/ready-resource/stop", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.StopReadyResourceScheduler)
|
||||||
|
api.POST("/scheduler/ready-resource/trigger", middleware.AuthMiddleware(), middleware.AdminMiddleware(), handlers.TriggerReadyResourceScheduler)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 静态文件服务
|
// 静态文件服务
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetGlobalScheduler 获取全局调度器实例(单例模式)
|
// GetGlobalScheduler 获取全局调度器实例(单例模式)
|
||||||
func GetGlobalScheduler(hotDramaRepo repo.HotDramaRepository) *GlobalScheduler {
|
func GetGlobalScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository) *GlobalScheduler {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
globalScheduler = &GlobalScheduler{
|
globalScheduler = &GlobalScheduler{
|
||||||
scheduler: NewScheduler(hotDramaRepo),
|
scheduler: NewScheduler(hotDramaRepo, readyResourceRepo, resourceRepo, systemConfigRepo),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return globalScheduler
|
return globalScheduler
|
||||||
@@ -67,6 +67,48 @@ func (gs *GlobalScheduler) GetHotDramaNames() ([]string, error) {
|
|||||||
return gs.scheduler.GetHotDramaNames()
|
return gs.scheduler.GetHotDramaNames()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartReadyResourceScheduler 启动待处理资源自动处理任务
|
||||||
|
func (gs *GlobalScheduler) StartReadyResourceScheduler() {
|
||||||
|
gs.mutex.Lock()
|
||||||
|
defer gs.mutex.Unlock()
|
||||||
|
|
||||||
|
if gs.scheduler.IsReadyResourceRunning() {
|
||||||
|
log.Println("待处理资源自动处理任务已在运行中")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gs.scheduler.StartReadyResourceScheduler()
|
||||||
|
log.Println("全局调度器已启动待处理资源自动处理任务")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopReadyResourceScheduler 停止待处理资源自动处理任务
|
||||||
|
func (gs *GlobalScheduler) StopReadyResourceScheduler() {
|
||||||
|
gs.mutex.Lock()
|
||||||
|
defer gs.mutex.Unlock()
|
||||||
|
|
||||||
|
if !gs.scheduler.IsReadyResourceRunning() {
|
||||||
|
log.Println("待处理资源自动处理任务未在运行")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gs.scheduler.StopReadyResourceScheduler()
|
||||||
|
log.Println("全局调度器已停止待处理资源自动处理任务")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReadyResourceRunning 检查待处理资源自动处理任务是否在运行
|
||||||
|
func (gs *GlobalScheduler) IsReadyResourceRunning() bool {
|
||||||
|
gs.mutex.RLock()
|
||||||
|
defer gs.mutex.RUnlock()
|
||||||
|
return gs.scheduler.IsReadyResourceRunning()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessReadyResources 手动触发待处理资源处理
|
||||||
|
func (gs *GlobalScheduler) ProcessReadyResources() {
|
||||||
|
gs.mutex.Lock()
|
||||||
|
defer gs.mutex.Unlock()
|
||||||
|
gs.scheduler.processReadyResources()
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateSchedulerStatus 根据系统配置更新调度器状态
|
// UpdateSchedulerStatus 根据系统配置更新调度器状态
|
||||||
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool) {
|
func (gs *GlobalScheduler) UpdateSchedulerStatus(autoFetchHotDramaEnabled bool) {
|
||||||
gs.mutex.Lock()
|
gs.mutex.Lock()
|
||||||
|
|||||||
@@ -1,28 +1,43 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"res_db/db/entity"
|
"res_db/db/entity"
|
||||||
"res_db/db/repo"
|
"res_db/db/repo"
|
||||||
|
"res_db/utils"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scheduler 定时任务管理器
|
// Scheduler 定时任务管理器
|
||||||
type Scheduler struct {
|
type Scheduler struct {
|
||||||
doubanService *DoubanService
|
doubanService *DoubanService
|
||||||
hotDramaRepo repo.HotDramaRepository
|
hotDramaRepo repo.HotDramaRepository
|
||||||
stopChan chan bool
|
readyResourceRepo repo.ReadyResourceRepository
|
||||||
isRunning bool
|
resourceRepo repo.ResourceRepository
|
||||||
|
systemConfigRepo repo.SystemConfigRepository
|
||||||
|
stopChan chan bool
|
||||||
|
isRunning bool
|
||||||
|
readyResourceRunning bool
|
||||||
|
processingMutex sync.Mutex // 防止ready_resource任务重叠执行
|
||||||
|
hotDramaMutex sync.Mutex // 防止热播剧任务重叠执行
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScheduler 创建新的定时任务管理器
|
// NewScheduler 创建新的定时任务管理器
|
||||||
func NewScheduler(hotDramaRepo repo.HotDramaRepository) *Scheduler {
|
func NewScheduler(hotDramaRepo repo.HotDramaRepository, readyResourceRepo repo.ReadyResourceRepository, resourceRepo repo.ResourceRepository, systemConfigRepo repo.SystemConfigRepository) *Scheduler {
|
||||||
return &Scheduler{
|
return &Scheduler{
|
||||||
doubanService: NewDoubanService(),
|
doubanService: NewDoubanService(),
|
||||||
hotDramaRepo: hotDramaRepo,
|
hotDramaRepo: hotDramaRepo,
|
||||||
stopChan: make(chan bool),
|
readyResourceRepo: readyResourceRepo,
|
||||||
isRunning: false,
|
resourceRepo: resourceRepo,
|
||||||
|
systemConfigRepo: systemConfigRepo,
|
||||||
|
stopChan: make(chan bool),
|
||||||
|
isRunning: false,
|
||||||
|
readyResourceRunning: false,
|
||||||
|
processingMutex: sync.Mutex{},
|
||||||
|
hotDramaMutex: sync.Mutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +61,15 @@ func (s *Scheduler) StartHotDramaScheduler() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
s.fetchHotDramaData()
|
// 使用TryLock防止任务重叠执行
|
||||||
|
if s.hotDramaMutex.TryLock() {
|
||||||
|
go func() {
|
||||||
|
defer s.hotDramaMutex.Unlock()
|
||||||
|
s.fetchHotDramaData()
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
log.Println("上一次热播剧任务还在执行中,跳过本次执行")
|
||||||
|
}
|
||||||
case <-s.stopChan:
|
case <-s.stopChan:
|
||||||
log.Println("停止热播剧定时任务")
|
log.Println("停止热播剧定时任务")
|
||||||
return
|
return
|
||||||
@@ -173,3 +196,200 @@ func (s *Scheduler) IsRunning() bool {
|
|||||||
func (s *Scheduler) GetHotDramaNames() ([]string, error) {
|
func (s *Scheduler) GetHotDramaNames() ([]string, error) {
|
||||||
return s.doubanService.FetchHotDramaNames()
|
return s.doubanService.FetchHotDramaNames()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartReadyResourceScheduler 启动待处理资源自动处理任务
|
||||||
|
func (s *Scheduler) StartReadyResourceScheduler() {
|
||||||
|
if s.readyResourceRunning {
|
||||||
|
log.Println("待处理资源自动处理任务已在运行中")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.readyResourceRunning = true
|
||||||
|
log.Println("启动待处理资源自动处理任务")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(5 * time.Minute) // 每5分钟检查一次
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// 立即执行一次
|
||||||
|
s.processReadyResources()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
// 使用TryLock防止任务重叠执行
|
||||||
|
if s.processingMutex.TryLock() {
|
||||||
|
go func() {
|
||||||
|
defer s.processingMutex.Unlock()
|
||||||
|
s.processReadyResources()
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
log.Println("上一次待处理资源任务还在执行中,跳过本次执行")
|
||||||
|
}
|
||||||
|
case <-s.stopChan:
|
||||||
|
log.Println("停止待处理资源自动处理任务")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopReadyResourceScheduler 停止待处理资源自动处理任务
|
||||||
|
func (s *Scheduler) StopReadyResourceScheduler() {
|
||||||
|
if !s.readyResourceRunning {
|
||||||
|
log.Println("待处理资源自动处理任务未在运行")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.stopChan <- true
|
||||||
|
s.readyResourceRunning = false
|
||||||
|
log.Println("已发送停止信号给待处理资源自动处理任务")
|
||||||
|
}
|
||||||
|
|
||||||
|
// processReadyResources 处理待处理资源
|
||||||
|
func (s *Scheduler) processReadyResources() {
|
||||||
|
log.Println("开始处理待处理资源...")
|
||||||
|
|
||||||
|
// 检查系统配置,确认是否启用自动处理
|
||||||
|
config, err := s.systemConfigRepo.GetOrCreateDefault()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取系统配置失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !config.AutoProcessReadyResources {
|
||||||
|
log.Println("自动处理待处理资源功能已禁用")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有待处理资源
|
||||||
|
readyResources, err := s.readyResourceRepo.FindAll()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("获取待处理资源失败: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(readyResources) == 0 {
|
||||||
|
log.Println("没有待处理的资源")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("找到 %d 个待处理资源,开始处理...", len(readyResources))
|
||||||
|
|
||||||
|
processedCount := 0
|
||||||
|
for _, readyResource := range readyResources {
|
||||||
|
|
||||||
|
//readyResource.URL 是 查重
|
||||||
|
exits, err := s.resourceRepo.FindExists(readyResource.URL)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("查重失败: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if exits {
|
||||||
|
log.Printf("资源已存在: %s", readyResource.URL)
|
||||||
|
s.readyResourceRepo.Delete(readyResource.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.convertReadyResourceToResource(readyResource); err != nil {
|
||||||
|
log.Printf("处理资源失败 (ID: %d): %v", readyResource.ID, err)
|
||||||
|
}
|
||||||
|
s.readyResourceRepo.Delete(readyResource.ID)
|
||||||
|
processedCount++
|
||||||
|
log.Printf("成功处理资源: %s", readyResource.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("待处理资源处理完成,共处理 %d 个资源", processedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertReadyResourceToResource 将待处理资源转换为正式资源
|
||||||
|
func (s *Scheduler) convertReadyResourceToResource(readyResource entity.ReadyResource) error {
|
||||||
|
// 这里可以添加你的自定义逻辑
|
||||||
|
// 例如:URL解析、标题提取、分类判断等
|
||||||
|
|
||||||
|
fmt.Println("run task")
|
||||||
|
shareID, serviceType := utils.ExtractShareId(readyResource.URL)
|
||||||
|
fmt.Println(shareID, serviceType)
|
||||||
|
if serviceType == utils.NotFound {
|
||||||
|
// 不支持的链接地址
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非夸克地址, 需要有标题, 否则直接放弃
|
||||||
|
if serviceType != utils.Quark {
|
||||||
|
// 阿里云盘
|
||||||
|
checkResult, _ := utils.CheckURL(readyResource.URL)
|
||||||
|
if !checkResult.Status {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// 可以添加
|
||||||
|
}
|
||||||
|
|
||||||
|
if serviceType == utils.Quark {
|
||||||
|
// 通过api 获取到夸克数据的详情
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理Title字段(可能为nil)
|
||||||
|
// var title string
|
||||||
|
// if readyResource.Title != nil {
|
||||||
|
// title = *readyResource.Title
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 基础转换逻辑
|
||||||
|
// resource := &entity.Resource{
|
||||||
|
// Title: title,
|
||||||
|
// Description: readyResource.Description,
|
||||||
|
// URL: readyResource.URL,
|
||||||
|
// // 根据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
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // 创建资源
|
||||||
|
// return s.resourceRepo.Create(resource)
|
||||||
|
|
||||||
|
// 临时返回nil,等待你添加自定义逻辑
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// determinePanID 根据URL确定平台ID
|
||||||
|
func (s *Scheduler) determinePanID(url string) *uint {
|
||||||
|
url = strings.ToLower(url)
|
||||||
|
|
||||||
|
// 这里可以根据你的平台配置来判断
|
||||||
|
// 示例逻辑,你需要根据实际情况调整
|
||||||
|
if strings.Contains(url, "pan.baidu.com") {
|
||||||
|
panID := uint(1) // 百度网盘
|
||||||
|
return &panID
|
||||||
|
} else if strings.Contains(url, "www.aliyundrive.com") {
|
||||||
|
panID := uint(2) // 阿里云盘
|
||||||
|
return &panID
|
||||||
|
} else if strings.Contains(url, "pan.quark.cn") {
|
||||||
|
panID := uint(3) // 夸克网盘
|
||||||
|
return &panID
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOrCreateCategory 获取或创建分类
|
||||||
|
func (s *Scheduler) getOrCreateCategory(categoryName string) (uint, error) {
|
||||||
|
// 这里需要实现分类的查找和创建逻辑
|
||||||
|
// 由于没有CategoryRepository的注入,这里先返回0
|
||||||
|
// 你可以根据需要添加CategoryRepository的依赖
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsReadyResourceRunning 检查待处理资源自动处理任务是否在运行
|
||||||
|
func (s *Scheduler) IsReadyResourceRunning() bool {
|
||||||
|
return s.readyResourceRunning
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user