refactor: 移除未使用的依赖和功能,优化代码结构

移除sharp模块及相关缩略图功能
删除webpack配置文件和安装脚本
清理未使用的依赖项
重构PHP服务端代码,优化缓存处理逻辑
This commit is contained in:
2025-09-01 17:06:35 +08:00
parent 64a9e5d52e
commit 95737ecab8
7 changed files with 498 additions and 375 deletions

View File

@@ -1,77 +0,0 @@
# AList 代理服务安装指南
本指南将指导您如何安装和运行 AList 代理服务。AList 是一个支持多种存储的文件列表程序,提供网页浏览和 WebDAV 服务。
## 安装步骤
### 一键安装方法, install.sh
```bash
curl -o install.sh https://git.x-php.com/XiaoMo/alist-proxy/raw/branch/master/install.sh && chmod +x install.sh && ./install.sh
curl -fsSL "https://git.x-php.com/XiaoMo/alist-proxy/raw/branch/master/install.sh" | bash
```
### 使用 Node.js 版本
1. **下载代理脚本**
使用以下命令将代理脚本下载到本地:
```bash
curl -o index.js https://git.x-php.com/XiaoMo/alist-proxy/raw/branch/master/index.js
```
2. **安装 Node.js**
确保您的系统已安装 Node.js。如果未安装请访问 [Node.js 官网](https://nodejs.org)下载并安装。
3. **运行代理服务**
下载完成后,使用以下命令启动代理服务:
```bash
node index.js
```
4. **访问 AList 界面**
代理服务启动后,您可以通过浏览器访问 AList 的 Web 界面。默认情况下AList 监听在 `http://localhost:9001`。
### 使用 PHP 版本
1. **下载代理脚本**
使用以下命令将代理脚本下载到本地:
```bash
curl -o index.php https://git.x-php.com/XiaoMo/alist-proxy/raw/branch/master/index.php
```
2. **运行代理服务**
使用 Swoole CLI 启动代理服务:
```bash
./swoole-cli ./index.php
```
3. **常驻后台运行**
为了让服务在后台持续运行,可以使用 `nohup` 命令:
```bash
nohup ./swoole-cli ./index.php > /dev/null 2>&1 &
```
4. **设置自动启动**
(此部分需要根据您的系统和需求进行具体配置,建议查阅相关文档以设置服务的自动启动。)
---
通过以上步骤,您可以成功安装并运行 AList 代理服务。如需进一步帮助,请参考官方文档或联系技术支持。

File diff suppressed because one or more lines are too long

602
index.php
View File

@@ -2,12 +2,36 @@
use Swoole\Coroutine\Http\Server; use Swoole\Coroutine\Http\Server;
use Swoole\Coroutine\Http\Client; use Swoole\Coroutine\Http\Client;
use Swoole\Coroutine; use function Swoole\Coroutine\run;
$port = 9001; // 常量定义
$apiEndpoint = 'https://oss.x-php.com/get/'; const CACHE_DIR_NAME = '.cache';
$cacheDir = __DIR__ . '/.cache'; const DEFAULT_PORT = 9001;
const DEFAULT_API_ENDPOINT = 'http://183.6.121.121:9519/api';
const CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24小时
const CACHE_CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // 1小时
const API_TIMEOUT_MS = 5000;
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36';
// HTTP状态码
const HTTP_STATUS = [
'OK' => 200,
'NO_CONTENT' => 204,
'REDIRECT' => 302,
'NOT_MODIFIED' => 304,
'BAD_REQUEST' => 400,
'NOT_FOUND' => 404,
'INTERNAL_SERVER_ERROR' => 500,
'BAD_GATEWAY' => 502,
];
// 初始化变量
$cacheDir = __DIR__ . '/' . CACHE_DIR_NAME;
$pathIndex = []; $pathIndex = [];
$port = DEFAULT_PORT;
$apiEndpoint = DEFAULT_API_ENDPOINT;
// 访问计数器
$viewsInfo = [ $viewsInfo = [
'request' => 0, 'request' => 0,
'cacheHit' => 0, 'cacheHit' => 0,
@@ -18,137 +42,501 @@ $viewsInfo = [
'fetchApiWarning' => 0, 'fetchApiWarning' => 0,
]; ];
// Ensure cache directory exists // 解析命令行参数
if (!is_dir($cacheDir)) { function parseArguments() {
mkdir($cacheDir, 0777, true); global $port, $apiEndpoint;
$options = getopt('', ['port:', 'api:']);
if (isset($options['port'])) {
$parsedPort = intval($options['port']);
if ($parsedPort > 0) {
$port = $parsedPort;
}
}
if (isset($options['api'])) {
$apiEndpoint = $options['api'];
}
} }
Swoole\Coroutine\run(function () use ($port, $apiEndpoint, $cacheDir, &$pathIndex, &$viewsInfo) { // 初始化应用
$server = new Server('0.0.0.0', $port, false); function initializeApp() {
global $cacheDir;
$server->set([ parseArguments();
'daemonize' => true,
if (!file_exists($cacheDir)) {
try {
mkdir($cacheDir, 0777, true);
echo "Cache directory created: {$cacheDir}\n";
} catch (Exception $e) {
echo "Error creating cache directory {$cacheDir}: " . $e->getMessage() . "\n";
exit(1);
}
}
}
// 发送错误响应
function sendErrorResponse($res, int $statusCode, string $message) {
if (!$res->isWritable()) {
return;
}
$res->status($statusCode);
$res->header('Content-Type', 'text/plain;charset=UTF-8');
$res->end($message);
}
// 处理favicon请求
function handleFavicon($req, $res) {
$res->status(HTTP_STATUS['NO_CONTENT']);
$res->end();
}
// 处理endpoint请求
function handleEndpoint($req, $res, array $queryParams) {
global $apiEndpoint, $port, $cacheDir, $pathIndex, $viewsInfo;
if (isset($queryParams['api'])) {
$urlRegex = '/^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w.-]*)*\/?$/';
if (preg_match($urlRegex, $queryParams['api'])) {
$apiEndpoint = $queryParams['api'];
echo "API endpoint updated to: {$apiEndpoint}\n";
}
}
$res->status(HTTP_STATUS['OK']);
$res->header('Content-Type', 'application/json; charset=utf-8');
$res->end(json_encode([
'code' => HTTP_STATUS['OK'],
'data' => [
'api' => $apiEndpoint,
'port' => $port,
'cacheDir' => $cacheDir,
'pathIndexCount' => count($pathIndex),
'viewsInfo' => $viewsInfo
]
]));
}
// 处理API重定向
function handleApiRedirect($res, array $apiData) {
$res->status(HTTP_STATUS['REDIRECT']);
$res->header('Location', $apiData['data']['url']);
$res->end();
}
// 检查缓存头并返回是否为304
function checkCacheHeaders($req, string $cacheMetaFile) {
try {
$metaContent = file_get_contents($cacheMetaFile);
$cacheData = json_decode($metaContent, true);
$ifNoneMatch = isset($req->header['if-none-match']) ? $req->header['if-none-match'] : null;
$ifModifiedSince = isset($req->header['if-modified-since']) ? $req->header['if-modified-since'] : null;
// 检查ETag
if ($ifNoneMatch && isset($cacheData['uniqid']) && $ifNoneMatch === $cacheData['uniqid']) {
return ['cacheData' => $cacheData, 'isNotModified' => true];
}
// 检查If-Modified-Since
if ($ifModifiedSince && isset($cacheData['headers']['last-modified'])) {
try {
$lastModifiedDate = strtotime($cacheData['headers']['last-modified']);
$ifModifiedSinceDate = strtotime($ifModifiedSince);
if ($lastModifiedDate <= $ifModifiedSinceDate) {
return ['cacheData' => $cacheData, 'isNotModified' => true];
}
} catch (Exception $e) {
echo "Error parsing date for cache header check ({$cacheMetaFile}): " . $e->getMessage() . "\n";
}
}
return ['cacheData' => $cacheData, 'isNotModified' => false];
} catch (Exception $e) {
echo "Error reading or parsing cache meta file {$cacheMetaFile} in checkCacheHeaders: " . $e->getMessage() . "\n";
return ['cacheData' => null, 'isNotModified' => false];
}
}
// 检查缓存是否有效
function isCacheValid(string $cacheMetaFile, string $cacheContentFile) {
if (!file_exists($cacheMetaFile) || !file_exists($cacheContentFile)) {
return false;
}
try {
$metaContent = file_get_contents($cacheMetaFile);
$cacheData = json_decode($metaContent, true);
return isset($cacheData['expiration']) && is_numeric($cacheData['expiration']) && $cacheData['expiration'] > time() * 1000;
} catch (Exception $e) {
echo "Error reading or parsing cache meta file {$cacheMetaFile} for validation: " . $e->getMessage() . "\n";
return false;
}
}
// 从API获取数据
function fetchApiData(string $reqPath, string $token, string $sign) {
global $apiEndpoint;
$queryParams = http_build_query([
'type' => $reqPath,
'sign' => $sign
]); ]);
$server->handle('/', function ($request, $response) use ($apiEndpoint, $cacheDir, &$pathIndex, &$viewsInfo) { $apiUrl = "{$apiEndpoint}?{$queryParams}";
$viewsInfo['request']++; $parsedApiUrl = parse_url($apiUrl);
$uri = $request->server['request_uri']; $client = new Client($parsedApiUrl['host'], $parsedApiUrl['port'] ?? ($parsedApiUrl['scheme'] === 'https' ? 443 : 80), $parsedApiUrl['scheme'] === 'https');
$parsedUrl = parse_url($uri); $client->setHeaders([
parse_str($parsedUrl['query'] ?? '', $query); 'Accept' => 'application/json; charset=utf-8',
'User-Agent' => USER_AGENT,
'token' => $token
]);
$client->set(['timeout' => API_TIMEOUT_MS / 1000]);
$reqPath = explode('/', trim($parsedUrl['path'], '/'))[0] ?? ''; $path = isset($parsedApiUrl['path']) ? $parsedApiUrl['path'] : '/';
$token = implode('/', array_slice(explode('/', trim($parsedUrl['path'], '/')), 1)); if (isset($parsedApiUrl['query'])) {
$path .= '?' . $parsedApiUrl['query'];
}
if ($reqPath === 'endpoint') { $client->get($path);
return sendJsonResponse($response, [
'code' => 200, if ($client->statusCode >= 400) {
'data' => [ echo "API request to {$apiUrl} failed with status {$client->statusCode}: {$client->body}\n";
'api' => $apiEndpoint, $errorPayload = ['code' => $client->statusCode, 'message' => "API Error: {$client->statusCode}"];
'port' => $port,
'cacheDir' => $cacheDir, try {
'pathIndexCount' => count($pathIndex), $parsedError = json_decode($client->body, true);
'viewsInfo' => $viewsInfo, if ($parsedError && isset($parsedError['message'])) {
], $errorPayload['message'] = $parsedError['message'];
]); }
} catch (Exception $e) {
// Ignore if response is not JSON
} }
if (empty($token) || $token === 'undefined') { return $errorPayload;
$token = $reqPath; }
$reqPath = 'go';
try {
return json_decode($client->body, true);
} catch (Exception $e) {
echo "Error parsing JSON response from {$apiUrl}: " . $e->getMessage() . ", {$client->body}\n";
throw new Exception("Failed to parse API response: " . $e->getMessage());
}
}
// 从缓存中读取数据并返回
function serveFromCache(array $cacheData, string $cacheContentFile, string $cacheMetaFile, $res) {
global $viewsInfo;
if (!$cacheData) {
echo "serveFromCache called with null cacheData for {$cacheContentFile}\n";
sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], 'Cache metadata unavailable.');
return;
}
$viewsInfo['cacheCall']++;
try {
$fileContent = file_get_contents($cacheContentFile);
if ($fileContent === false) {
throw new Exception("Failed to read cache file");
} }
if (!in_array($reqPath, ['avatar', 'go', 'bbs', 'www', 'url', 'thumb'])) { $baseHeaders = [
return sendErrorResponse($response, 404, 'Not Found'); 'Cloud-Type' => $cacheData['cloudtype'] ?? 'unknown',
'Cloud-Expiration' => $cacheData['expiration'] ?? 0,
'ETag' => $cacheData['uniqid'] ?? '',
'Cache-Control' => 'public, max-age=31536000',
'Expires' => gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT',
'Accept-Ranges' => 'bytes',
'Connection' => 'keep-alive',
'Date' => gmdate('D, d M Y H:i:s') . ' GMT',
'Last-Modified' => isset($cacheData['headers']['last-modified']) ? $cacheData['headers']['last-modified'] : gmdate('D, d M Y H:i:s', filemtime($cacheMetaFile)) . ' GMT',
];
$isVideo = isset($cacheData['path']) && is_string($cacheData['path']) && strpos($cacheData['path'], '.mp4') !== false;
$contentType = isset($cacheData['headers']['Content-Type']) ? $cacheData['headers']['Content-Type'] : ($isVideo ? 'video/mp4' : 'application/octet-stream');
$responseHeaders = array_merge($baseHeaders, [
'Content-Type' => $contentType,
]);
foreach ($responseHeaders as $key => $value) {
$res->header($key, $value);
} }
if (!$token) { $res->status(HTTP_STATUS['OK']);
return sendErrorResponse($response, 400, 'Bad Request: Missing Token or path'); $res->end($fileContent);
} catch (Exception $e) {
$viewsInfo['cacheReadError']++;
echo "Error reading from cache {$cacheContentFile}: " . $e->getMessage() . "\n";
sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], "Failed to read from cache: " . $e->getMessage());
}
}
// 从真实URL获取数据并写入缓存
function fetchAndServe(array $data, string $tempCacheContentFile, string $cacheContentFile, string $cacheMetaFile, $res) {
global $viewsInfo;
$parsedUrl = parse_url($data['realUrl']);
$client = new Client($parsedUrl['host'], $parsedUrl['port'] ?? ($parsedUrl['scheme'] === 'https' ? 443 : 80), $parsedUrl['scheme'] === 'https');
$client->setHeaders([
'User-Agent' => USER_AGENT
]);
$path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '/';
if (isset($parsedUrl['query'])) {
$path .= '?' . $parsedUrl['query'];
}
$client->get($path);
if ($client->statusCode !== 200) {
echo "Error fetching from {$data['realUrl']}: HTTP status {$client->statusCode}\n";
sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], "Bad Gateway: Failed to fetch content from source");
return;
}
$isVideo = isset($data['path']) && is_string($data['path']) && strpos($data['path'], '.mp4') !== false;
// 检查content-length
$contentLength = isset($client->headers['content-length']) ? $client->headers['content-length'] : null;
if ($contentLength) {
// contentLength小于2KB且与缓存文件大小不一致时重新获取
if ($contentLength < 2048 && isset($data['headers']['content-length']) && $data['headers']['content-length'] !== $contentLength) {
echo "Warning: content-length is different for the response from: {$data['realUrl']}\n";
sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], "Bad Gateway: Content-Length mismatch for {$data['realUrl']}");
return;
} }
// 更新data到缓存cacheMetaFile
file_put_contents($cacheMetaFile, json_encode($data));
} else {
echo "Warning: content-length is undefined for the response from: {$data['realUrl']}\n";
}
$uniqidhex = md5($reqPath . $token . ($query['sign'] ?? '')); // 写入临时缓存文件
file_put_contents($tempCacheContentFile, $client->body);
if (isset($pathIndex[$uniqidhex]) && isCacheValid($cacheDir, $pathIndex[$uniqidhex])) { // 重命名临时文件为正式缓存文件
try {
$targetDir = dirname($cacheContentFile);
if (!file_exists($targetDir)) {
mkdir($targetDir, 0777, true);
}
rename($tempCacheContentFile, $cacheContentFile);
echo "Successfully cached: {$cacheContentFile}\n";
} catch (Exception $e) {
echo "Error renaming temp cache file {$tempCacheContentFile} to {$cacheContentFile}: " . $e->getMessage() . "\n";
// 如果重命名失败,尝试删除临时文件以避免混乱
if (file_exists($tempCacheContentFile)) {
unlink($tempCacheContentFile);
}
}
$baseHeaders = [
'Cloud-Type' => $data['cloudtype'] ?? 'unknown',
'Cloud-Expiration' => $data['expiration'] ?? 0,
'ETag' => $data['uniqid'] ?? '',
'Cache-Control' => 'public, max-age=31536000',
'Expires' => gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT',
'Accept-Ranges' => 'bytes',
'Connection' => 'keep-alive',
'Date' => gmdate('D, d M Y H:i:s') . ' GMT',
'Last-Modified' => isset($data['headers']['last-modified']) ?
$data['headers']['last-modified'] :
gmdate('D, d M Y H:i:s', filemtime($cacheMetaFile)) . ' GMT',
];
$responseHeaders = array_merge($baseHeaders, [
'Content-Type' => isset($client->headers['content-type']) ? $client->headers['content-type'] : ($isVideo ? 'video/mp4' : 'application/octet-stream'),
], $data['headers']);
foreach ($responseHeaders as $key => $value) {
$res->header($key, $value);
}
$res->status(HTTP_STATUS['OK']);
$res->end($client->body);
}
// 尝试从过期缓存提供服务或返回错误
function tryServeFromStaleCacheOrError(string $uniqidhex, $res, string $errorMessage = null) {
global $pathIndex, $cacheDir;
if (isset($pathIndex[$uniqidhex])) {
$cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta';
$cacheContentFile = $cacheDir . '/' . $pathIndex[$uniqidhex]['uniqid'] . '.content';
if (file_exists($cacheMetaFile) && file_exists($cacheContentFile)) {
echo "API call failed or returned non-200. Serving stale cache for {$uniqidhex}\n";
try {
$cacheData = json_decode(file_get_contents($cacheMetaFile), true);
serveFromCache($cacheData, $cacheContentFile, $cacheMetaFile, $res);
return;
} catch (Exception $e) {
echo "Error parsing stale meta file {$cacheMetaFile}: " . $e->getMessage() . "\n";
// 如果过期缓存也损坏,则返回通用错误
}
}
}
sendErrorResponse($res, HTTP_STATUS['BAD_GATEWAY'], $errorMessage ?: 'Bad Gateway');
}
// 处理主请求
function handleMainRequest($req, $res) {
global $pathIndex, $cacheDir, $viewsInfo;
$url = preg_replace('/\/{2,}/', '/', $req->server['request_uri']);
$parsedUrl = parse_url($url);
$queryParams = [];
if (isset($parsedUrl['query'])) {
parse_str($parsedUrl['query'], $queryParams);
}
$sign = $queryParams['sign'] ?? '';
$pathParts = explode('/', trim($parsedUrl['path'], '/'));
$reqPath = $pathParts[0] ?? '';
$token = implode('/', array_slice($pathParts, 1));
if ($reqPath === 'favicon.ico') {
return handleFavicon($req, $res);
}
if ($reqPath === 'endpoint') {
return handleEndpoint($req, $res, $queryParams);
}
if (!$token && $reqPath) {
$token = $reqPath;
$reqPath = 'app'; // 默认为'app',如果只提供了一个路径段
}
$allowedPaths = ['avatar', 'go', 'bbs', 'www', 'url', 'thumb', 'app'];
if (!in_array($reqPath, $allowedPaths) || !$token) {
return sendErrorResponse($res, HTTP_STATUS['BAD_REQUEST'], "Bad Request: Invalid path or missing token.");
}
$viewsInfo['request']++;
$uniqidhex = md5($reqPath . $token . $sign);
$cacheMetaFile = '';
$cacheContentFile = '';
if (isset($pathIndex[$uniqidhex])) {
$cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta';
$cacheContentFile = $cacheDir . '/' . $pathIndex[$uniqidhex]['uniqid'] . '.content';
}
if (isset($pathIndex[$uniqidhex]) && isCacheValid($cacheMetaFile, $cacheContentFile)) {
$cacheResult = checkCacheHeaders($req, $cacheMetaFile);
if ($cacheResult['isNotModified']) {
$res->status(HTTP_STATUS['NOT_MODIFIED']);
$res->end();
} else {
$viewsInfo['cacheHit']++; $viewsInfo['cacheHit']++;
return serveFromCache($cacheDir, $pathIndex[$uniqidhex], $response); serveFromCache($cacheResult['cacheData'], $cacheContentFile, $cacheMetaFile, $res);
} }
} else {
try {
$viewsInfo['apiCall']++;
$apiData = fetchApiData($reqPath, $token, $sign);
$viewsInfo['apiCall']++; if (isset($apiData['code']) && ($apiData['code'] === HTTP_STATUS['REDIRECT'] || $apiData['code'] === 301)) {
fetchApiData($apiEndpoint, $reqPath, $token, $query['sign'] ?? '', function ($apiData) use ($cacheDir, &$pathIndex, $uniqidhex, $response) { return handleApiRedirect($res, $apiData);
if ($apiData['code'] === 200 && isset($apiData['data']['url'])) { }
$pathIndex[$uniqidhex] = $uniqidhex;
if (isCacheValid($cacheDir, $uniqidhex)) { if (isset($apiData['code']) && $apiData['code'] === HTTP_STATUS['OK'] && isset($apiData['data']) && isset($apiData['data']['url'])) {
serveFromCache($cacheDir, $uniqidhex, $response); $data = [
'realUrl' => $apiData['data']['url'],
'cloudtype' => $apiData['data']['cloudtype'] ?? '',
'expiration' => isset($apiData['data']['expiration']) ? $apiData['data']['expiration'] * 1000 : 0,
'path' => $apiData['data']['path'] ?? '',
'headers' => $apiData['data']['headers'] ?? [],
'uniqid' => $apiData['data']['uniqid'] ?? '',
'thumb' => $apiData['data']['thumb'] ?? ''
];
$pathIndex[$uniqidhex] = ['uniqid' => $data['uniqid'], 'timestamp' => time() * 1000];
$cacheMetaFile = $cacheDir . '/' . $uniqidhex . '.meta';
$cacheContentFile = $cacheDir . '/' . $data['uniqid'] . '.content';
$tempCacheContentFile = $cacheDir . '/' . $data['uniqid'] . '_' . bin2hex(random_bytes(16)) . '.temp';
try {
file_put_contents($cacheMetaFile, json_encode($data));
} catch (Exception $e) {
echo "Error writing meta file {$cacheMetaFile}: " . $e->getMessage() . "\n";
sendErrorResponse($res, HTTP_STATUS['INTERNAL_SERVER_ERROR'], 'Failed to write cache metadata.');
return; return;
} }
// 写入缓存 if (file_exists($cacheContentFile)) {
file_put_contents("$cacheDir/$uniqidhex.meta", json_encode($apiData['data'])); $contentLength = filesize($cacheContentFile);
if ($contentLength < 2048 && isset($data['headers']['content-length']) && intval($data['headers']['content-length']) !== $contentLength) {
// 获取文件内容, curl echo "Content length mismatch for {$cacheContentFile}. API: {$data['headers']['content-length']}, Cache: {$contentLength}. Re-fetching.\n";
$ch = curl_init(); fetchAndServe($data, $tempCacheContentFile, $cacheContentFile, $cacheMetaFile, $res);
curl_setopt($ch, CURLOPT_URL, $apiData['data']['url']); } else {
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); serveFromCache($data, $cacheContentFile, $cacheMetaFile, $res);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 50); }
$fileContent = curl_exec($ch); } else {
curl_close($ch); fetchAndServe($data, $tempCacheContentFile, $cacheContentFile, $cacheMetaFile, $res);
file_put_contents("$cacheDir/$uniqidhex.content", $fileContent); }
serveFromCache($cacheDir, $uniqidhex, $response);
} else { } else {
sendErrorResponse($response, 502, 'Bad Gateway: ' . json_encode($apiData)); $viewsInfo['fetchApiWarning']++;
tryServeFromStaleCacheOrError($uniqidhex, $res, $apiData['message'] ?? null);
} }
}); } catch (Exception $e) {
$viewsInfo['fetchApiError']++;
echo "Error in API call or processing: " . $e->getMessage() . "\n";
tryServeFromStaleCacheOrError($uniqidhex, $res, "Bad Gateway: API request failed. " . $e->getMessage());
}
}
}
// 定时清理过期缓存数据
function cleanupExpiredCache() {
global $pathIndex;
$currentTime = time() * 1000;
foreach ($pathIndex as $key => $value) {
if ($currentTime - $value['timestamp'] > CACHE_EXPIRY_MS) {
unset($pathIndex[$key]);
}
}
}
// 主函数
function main() {
global $port;
initializeApp();
// 创建服务器
$server = new Server('0.0.0.0', $port);
echo "Server started at http://0.0.0.0:{$port}\n";
// 设置定时器清理过期缓存
Swoole\Timer::tick(CACHE_CLEANUP_INTERVAL_MS, function () {
cleanupExpiredCache();
}); });
// 处理请求
$server->handle('/', function ($req, $res) {
handleMainRequest($req, $res);
});
$server->start(); $server->start();
}
// 启动服务器
run(function () {
main();
}); });
function isCacheValid($cacheDir, $cacheFile) {
return file_exists("$cacheDir/$cacheFile.meta") && file_exists("$cacheDir/$cacheFile.content");
}
function serveFromCache($cacheDir, $cacheFile, $response) {
$cacheData = json_decode(file_get_contents("$cacheDir/$cacheFile.meta"), true);
$response->header('Content-Type', $cacheData['headers']['Content-Type'] ?? 'application/octet-stream');
$response->header('Cloud-Type', $cacheData['cloudtype']);
$response->header('Cloud-Expiration', $cacheData['expiration']);
$response->header('ETag', $cacheData['uniqid'] ?? '');
$response->header('Cache-Control', 'public, max-age=31536000');
$response->header('Expires', gmdate('D, d M Y H:i:s', time() + 31536000000) . ' GMT');
$response->header('Accept-Ranges', 'bytes');
$response->header('Connection', 'keep-alive');
$response->header('Date', gmdate('D, d M Y H:i:s') . ' GMT');
$response->header('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
// 高效的读取文件
$file = fopen("$cacheDir/$cacheFile.content", 'r');
$response->end(fread($file, filesize("$cacheDir/$cacheFile.content")));
fclose($file);
}
function fetchApiData($apiEndpoint, $reqPath, $token, $sign, $callback) {
Coroutine::create(function () use ($apiEndpoint, $reqPath, $token, $sign, $callback) {
$client = new Client(parse_url($apiEndpoint, PHP_URL_HOST), 443, true);
$client->setHeaders([
'Accept' => 'application/json',
'User-Agent' => 'Swoole-Client',
'token' => $token,
]);
$client->get(parse_url($apiEndpoint, PHP_URL_PATH) . "?type=$reqPath&sign=$sign");
$callback(json_decode($client->body, true));
$client->close();
});
}
function sendJsonResponse($response, $data) {
$response->header('Content-Type', 'application/json');
$response->end(json_encode($data));
}
function sendErrorResponse($response, $status, $message) {
$response->status($status);
$response->end($message);
}

View File

@@ -1,110 +0,0 @@
#!/bin/bash
# Determine OS and architecture
OS=$(uname -s)
ARCH=$(uname -m)
# swoole-cli 版本号
VERSION="5.1.4"
# Set the download URL based on OS and architecture
case "$OS" in
Linux)
case "$ARCH" in
arm64)
FILE_URL="https://wenda-1252906962.file.myqcloud.com/dist/swoole-cli-v${VERSION}-linux-arm64.tar.xz"
;;
x86_64)
FILE_URL="https://wenda-1252906962.file.myqcloud.com/dist/swoole-cli-v${VERSION}-linux-x64.tar.xz"
;;
*)
echo "Unsupported architecture: $ARCH"
exit 1
;;
esac
;;
Darwin)
case "$ARCH" in
arm64)
FILE_URL="https://wenda-1252906962.file.myqcloud.com/dist/swoole-cli-v${VERSION}-macos-arm64.tar.xz"
;;
x86_64)
FILE_URL="https://wenda-1252906962.file.myqcloud.com/dist/swoole-cli-v${VERSION}-macos-x64.tar.xz"
;;
*)
echo "Unsupported architecture: $ARCH"
exit 1
;;
esac
;;
*)
echo "Unsupported OS: $OS"
exit 1
;;
esac
# Check if Node.js is installed
if command -v node &> /dev/null; then
echo "Node.js is installed. Proceeding with Node.js script setup."
# Download the Node.js proxy script
echo "Downloading index.js..."
if ! curl -o "index.js" "https://git.x-php.com/XiaoMo/alist-proxy/raw/branch/master/index.js"; then
echo "Failed to download index.js. Please check your internet connection and try again."
exit 1
fi
# Run the Node.js proxy service
echo "Running Node.js proxy service..."
nohup node index.js > /dev/null 2>&1 &
echo "Node.js proxy service running in the background. You can check the logs with 'tail -f nohup.out'."
else
echo "Node.js is not installed. Proceeding with swoole-cli setup."
# Set the fixed file name
FILE_NAME="swoole-cli.tar.xz"
# Create a temporary directory
TEMP_DIR=$(mktemp -d)
mkdir -p "$TEMP_DIR"
# Download the file to the temporary directory
echo "Downloading $FILE_NAME from $FILE_URL to $TEMP_DIR..."
if ! curl -o "$TEMP_DIR/$FILE_NAME" "$FILE_URL"; then
echo "Download failed. Please check your internet connection and try again."
exit 1
fi
# Verify the download
if [ ! -f "$TEMP_DIR/$FILE_NAME" ]; then
echo "Downloaded file not found in $TEMP_DIR. Download may have failed."
exit 1
fi
# Extract the file to the temporary directory
tar -xf "$TEMP_DIR/$FILE_NAME" -C "$TEMP_DIR" || {
echo "Extraction failed. Please make sure you have the necessary tools to extract the file."
exit 1
}
# Move the extracted file to the current directory
mv "$TEMP_DIR"/swoole-cli .
# Remove unnecessary files
echo "Removing unnecessary files..."
rm -rf "$TEMP_DIR"
# Check if index.php file exists, if not, download it
if [ ! -f "index.php" ]; then
echo "Downloading index.php..."
if ! curl -o "index.php" "https://git.x-php.com/XiaoMo/alist-proxy/raw/branch/master/index.php"; then
echo "Failed to download index.php. Please check your internet connection and try again."
exit 1
fi
fi
# Run the PHP indexer
echo "Running PHP indexer..."
nohup ./swoole-cli ./index.php > /dev/null 2>&1 &
echo "PHP indexer running in the background. You can check the logs with 'tail -f nohup.out'."
fi

View File

@@ -1,15 +1,5 @@
{ {
"dependencies": { "dependencies": {
"javascript-obfuscator": "^4.1.1", "javascript-obfuscator": "^4.1.1"
"sharp": "^0.34.2"
},
"devDependencies": {
"webpack": "^5.99.9",
"webpack-cli": "^4.10.0",
"webpack-obfuscator": "^3.5.1"
},
"scripts": {
"build": "webpack",
"start": "node index.js"
} }
} }

View File

@@ -5,11 +5,10 @@ const querystring = require('querystring');
const fs = require('fs'); const fs = require('fs');
const pathModule = require('path'); const pathModule = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const sharp = require('sharp');
const CACHE_DIR_NAME = '.cache'; const CACHE_DIR_NAME = '.cache';
const DEFAULT_PORT = 9001; const DEFAULT_PORT = 9001;
const DEFAULT_API_ENDPOINT = 'http://183.6.121.121:9521/alist'; const DEFAULT_API_ENDPOINT = 'http://183.6.121.121:9519/api';
const cacheDir = pathModule.join(__dirname, CACHE_DIR_NAME); const cacheDir = pathModule.join(__dirname, CACHE_DIR_NAME);
const pathIndex = {}; const pathIndex = {};
@@ -375,23 +374,6 @@ async function fetchApiData(reqPath, token, sign) {
}); });
} }
// createThumbnail
function createThumbnail(data, cacheContentFile) {
const { path, thumb } = data;
const thumbCacheFile = pathModule.join(cacheDir, `thumb_${thumb.uniqid}.jpeg`);
if (fs.existsSync(thumbCacheFile)) return thumbCacheFile;
const isVideo = path && typeof path === 'string' && path.includes('.mp4');
if (isVideo || !thumb) return;
const width = thumb.width && thumb.width > 0 ? thumb.width : undefined;
const height = thumb.height && thumb.height > 0 ? thumb.height : undefined;
if (!width) return;
sharp(cacheContentFile).resize(width, height).toFile(thumbCacheFile);
return thumbCacheFile;
}
// 从真实 URL 获取数据并写入缓存 // 从真实 URL 获取数据并写入缓存
const REAL_URL_FETCH_TIMEOUT_MS = 0; // 0 means no timeout for the actual file download const REAL_URL_FETCH_TIMEOUT_MS = 0; // 0 means no timeout for the actual file download
@@ -457,10 +439,6 @@ const fetchAndServe = (data, tempCacheContentFile, cacheContentFile, cacheMetaFi
fs.renameSync(tempCacheContentFile, cacheContentFile); fs.renameSync(tempCacheContentFile, cacheContentFile);
console.log(`Successfully cached: ${cacheContentFile}`); console.log(`Successfully cached: ${cacheContentFile}`);
// 生成缩略图
if (data.thumb) {
createThumbnail(data, cacheContentFile);
}
} catch (renameError) { } catch (renameError) {
console.error(`Error renaming temp cache file ${tempCacheContentFile} to ${cacheContentFile}:`, renameError); console.error(`Error renaming temp cache file ${tempCacheContentFile} to ${cacheContentFile}:`, renameError);
// If rename fails, try to remove the temp file to avoid clutter // If rename fails, try to remove the temp file to avoid clutter
@@ -505,22 +483,6 @@ function serveFromCache(cacheData, cacheContentFile, cacheMetaFile, res) {
'Date': new Date().toUTCString(), 'Date': new Date().toUTCString(),
'Last-Modified': (cacheData.headers && cacheData.headers['last-modified']) || new Date(fs.statSync(cacheMetaFile).mtime).toUTCString(), 'Last-Modified': (cacheData.headers && cacheData.headers['last-modified']) || new Date(fs.statSync(cacheMetaFile).mtime).toUTCString(),
}; };
if (cacheData.thumb) {
var thumbCacheFile = createThumbnail(cacheData, cacheContentFile)
if (thumbCacheFile && fs.existsSync(thumbCacheFile)) {
cacheData.headers['content-length'] = fs.statSync(thumbCacheFile).size;
const responseHeaders = {
...baseHeaders,
...(cacheData.headers || {}),
'ETag': (cacheData.thumb.uniqid || cacheData.uniqid) + '_thumb',
'Content-Type': 'image/jpeg',
};
res.writeHead(HTTP_STATUS.OK, responseHeaders);
const thumbStream = fs.createReadStream(thumbCacheFile);
thumbStream.pipe(res);
return;
}
}
viewsInfo.increment('cacheCall'); viewsInfo.increment('cacheCall');
const readStream = fs.createReadStream(cacheContentFile); const readStream = fs.createReadStream(cacheContentFile);

View File

@@ -1,30 +0,0 @@
const path = require('path');
const WebpackObfuscator = require('webpack-obfuscator');
module.exports = {
entry: './source.js',
target: 'node',
output: {
filename: 'index.js',
path: path.resolve(__dirname)
},
plugins: [
new WebpackObfuscator({
compact: true,
controlFlowFlattening: true,
deadCodeInjection: true,
numbersToExpressions: true,
simplify: true,
splitStrings: true,
stringArray: true
})
],
optimization: {
minimize: true
},
// 添加以下配置来处理 sharp 模块
externals: {
sharp: 'commonjs sharp'
},
mode: 'production' // 添加 mode 配置来解决警告
};