This commit is contained in:
2025-11-27 19:20:46 +08:00
parent 7cba8ae8b1
commit 2838d6163e
37 changed files with 2553 additions and 256 deletions

View File

@@ -0,0 +1,165 @@
import { pipeline, Readable } from 'node:stream';
import { promisify } from 'node:util';
import { bucketName, minioClient, minioResources } from '@/modules/minio.ts';
import fs from 'node:fs';
import { IncomingMessage, ServerResponse } from 'node:http';
import http from 'node:http';
import https from 'node:https';
import { UserApp } from '@/modules/user-app/index.ts';
import { addStat } from '@/modules/html/stat/index.ts';
import path from 'path';
import { getTextContentType } from '@/modules/fm-manager/index.ts';
import { logger } from '@/modules/logger.ts';
const pipelineAsync = promisify(pipeline);
export async function downloadFileFromMinio(fileUrl: string, destFile: string) {
const objectName = fileUrl.replace(minioResources + '/', '');
const objectStream = await minioClient.getObject(bucketName, objectName);
const destStream = fs.createWriteStream(destFile);
await pipelineAsync(objectStream, destStream);
console.log(`minio File downloaded to ${minioResources}/${objectName} \n ${destFile}`);
}
export const filterKeys = (metaData: Record<string, string>, clearKeys: string[] = []) => {
const keys = Object.keys(metaData);
// remove X-Amz- meta data
const removeKeys = ['password', 'accesskey', 'secretkey', ...clearKeys];
const filteredKeys = keys.filter((key) => !removeKeys.includes(key));
return filteredKeys.reduce((acc, key) => {
acc[key] = metaData[key];
return acc;
}, {} as Record<string, string>);
};
export async function minioProxy(
req: IncomingMessage,
res: ServerResponse,
opts: {
proxyUrl: string;
createNotFoundPage: (msg?: string) => any;
isDownload?: boolean;
},
) {
const fileUrl = opts.proxyUrl;
const { createNotFoundPage, isDownload = false } = opts;
const objectName = fileUrl.replace(minioResources + '/', '');
try {
const stat = await minioClient.statObject(bucketName, objectName);
if (stat.size === 0) {
createNotFoundPage('Invalid proxy url');
return true;
}
const filterMetaData = filterKeys(stat.metaData, ['size', 'etag', 'last-modified']);
const contentLength = stat.size;
const etag = stat.etag;
const lastModified = stat.lastModified.toISOString();
const fileName = objectName.split('/').pop();
const ext = path.extname(fileName || '');
const objectStream = await minioClient.getObject(bucketName, objectName);
const headers = {
'Content-Length': contentLength,
etag,
'last-modified': lastModified,
'file-name': fileName,
...filterMetaData,
...getTextContentType(ext),
};
if (objectName.endsWith('.html') && !isDownload) {
const { html, contentLength } = await getTextFromStreamAndAddStat(objectStream);
res.writeHead(200, {
...headers,
'Content-Length': contentLength,
});
res.end(html);
} else {
res.writeHead(200, {
...headers,
});
objectStream.pipe(res, { end: true });
}
return true;
} catch (error) {
console.error(`Proxy request error: ${error.message}`);
createNotFoundPage('Invalid proxy url');
return false;
}
}
// 添加一个辅助函数来从流中获取文本
async function getTextFromStream(stream: Readable | IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let data = '';
stream.on('data', (chunk) => {
data += chunk;
});
stream.on('end', () => {
resolve(data);
});
stream.on('error', (err) => {
reject(err);
});
});
}
export async function getTextFromStreamAndAddStat(stream: Readable | IncomingMessage): Promise<{ html: string; contentLength: number }> {
const html = await getTextFromStream(stream);
const newHtml = addStat(html);
const newContentLength = Buffer.byteLength(newHtml);
return { html: newHtml, contentLength: newContentLength };
}
export const httpProxy = async (
req: IncomingMessage,
res: ServerResponse,
opts: {
proxyUrl: string;
userApp: UserApp;
createNotFoundPage: (msg?: string) => any;
},
) => {
const { proxyUrl, userApp, createNotFoundPage } = opts;
const _u = new URL(req.url, 'http://localhost');
const params = _u.searchParams;
const isDownload = params.get('download') === 'true';
if (proxyUrl.startsWith(minioResources)) {
const isOk = await minioProxy(req, res, { ...opts, isDownload });
if (!isOk) {
userApp.clearCacheData();
}
return;
}
let protocol = proxyUrl.startsWith('https') ? https : http;
// 代理
const proxyReq = protocol.request(proxyUrl, async (proxyRes) => {
const headers = proxyRes.headers;
if (proxyRes.statusCode === 404) {
userApp.clearCacheData();
return createNotFoundPage('Invalid proxy url');
}
if (proxyRes.statusCode === 302) {
res.writeHead(302, { Location: proxyRes.headers.location });
return res.end();
}
if (proxyUrl.endsWith('.html') && !isDownload) {
try {
const { html, contentLength } = await getTextFromStreamAndAddStat(proxyRes);
res.writeHead(200, {
...headers,
'Content-Length': contentLength,
});
res.end(html);
} catch (error) {
console.error(`Proxy request error: ${error.message}`);
return createNotFoundPage('Invalid proxy url:' + error.message);
}
} else {
res.writeHead(proxyRes.statusCode, {
...headers,
});
proxyRes.pipe(res, { end: true });
}
});
proxyReq.on('error', (err) => {
console.error(`Proxy request error: ${err.message}`);
userApp.clearCacheData();
});
proxyReq.end();
};