refactor: 移除未使用的依赖和功能,优化代码结构
移除sharp模块及相关缩略图功能 删除webpack配置文件和安装脚本 清理未使用的依赖项 重构PHP服务端代码,优化缓存处理逻辑
This commit is contained in:
77
README.md
77
README.md
@@ -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 代理服务。如需进一步帮助,请参考官方文档或联系技术支持。
|
|
||||||
602
index.php
602
index.php
@@ -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);
|
|
||||||
}
|
|
||||||
|
|||||||
110
install.sh
110
install.sh
@@ -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
|
|
||||||
12
package.json
12
package.json
@@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
40
source.js
40
source.js
@@ -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);
|
||||||
|
|||||||
@@ -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 配置来解决警告
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user