diff --git a/package.json b/package.json index 39a47bf..d8175af 100644 --- a/package.json +++ b/package.json @@ -30,28 +30,28 @@ "author": "", "license": "ISC", "devDependencies": { - "@rollup/plugin-commonjs": "^28.0.2", + "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-json": "^6.1.0", - "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-typescript": "^12.1.2", "@types/http-proxy": "^1.17.16", - "@types/node": "^22.13.9", + "@types/node": "^22.13.10", "@types/send": "^0.17.4", "concurrently": "^9.1.2", "cross-env": "^7.0.3", "nodemon": "^3.1.9", - "rollup": "^4.34.9", + "rollup": "^4.35.0", "tslib": "^2.8.1", "typescript": "^5.8.2" }, "dependencies": { - "@kevisual/code-center-module": "0.0.11-alpha.3", + "@kevisual/code-center-module": "0.0.13", "@kevisual/router": "0.0.9", "@kevisual/use-config": "^1.0.9", "archiver": "^7.0.1", "ioredis": "^5.6.0", "minio": "^8.0.4", - "nanoid": "^5.1.2", + "nanoid": "^5.1.3", "send": "^1.1.0", "sequelize": "^6.37.6" }, diff --git a/src/module/get-user-app.ts b/src/module/get-user-app.ts index a99a786..7c2fd55 100644 --- a/src/module/get-user-app.ts +++ b/src/module/get-user-app.ts @@ -8,6 +8,8 @@ import { pipeline } from 'stream'; import { promisify } from 'util'; import { fetchApp, fetchDomain, fetchTest } from './query/get-router.ts'; import { getAppLoadStatus, setAppLoadStatus, AppLoadStatus } from './redis/get-app-status.ts'; +import { bucketName, minioClient, minioResources } from './minio.ts'; +import { downloadFileFromMinio } from './proxy/http-proxy.ts'; const pipelineAsync = promisify(pipeline); @@ -391,13 +393,18 @@ export const deleteUserAppFiles = async (user: string, app: string) => { } // console.log('deleteUserAppFiles', res); }; + async function downloadFile(fileUrl: string, destFile: string) { + if (fileUrl.startsWith(minioResources)) { + await downloadFileFromMinio(fileUrl, destFile); + return; + } + console.log('destFile', destFile, 'fileUrl', fileUrl); const res = await fetch(fileUrl); if (!res.ok) { throw new Error(`Failed to fetch ${fileUrl}: ${res.statusText}`); } - console.log('destFile', destFile); const destStream = fs.createWriteStream(destFile); // 使用 `pipeline` 将 `res.body` 中的数据传递给 `destStream` diff --git a/src/module/index.ts b/src/module/index.ts index 8029e6f..1a094f8 100644 --- a/src/module/index.ts +++ b/src/module/index.ts @@ -9,6 +9,7 @@ import { getContentType } from './get-content-type.ts'; import { createRefreshHtml } from './html/create-refresh-html.ts'; import { fileProxy } from './proxy/file-proxy.ts'; import net from 'net'; +import { httpProxy } from './proxy/http-proxy.ts'; const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' }; const domain = config?.proxy?.domain || 'kevisual.xiongxiao.me'; @@ -125,7 +126,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR const url = pathname; if (!domainApp && noProxyUrl.includes(url)) { if (url === '/') { - // 获取一下登陆用户,如果没有登陆用户,重定向到ai-chat页面 + // TODO: 获取一下登陆用户,如果没有登陆用户,重定向到ai-chat页面 // 重定向到 res.writeHead(302, { Location: home }); return res.end(); @@ -168,7 +169,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR res.write('Server Error\n'); res.end(); }; - const createNotFoundPage = (msg?: string) => { + const createNotFoundPage = async (msg?: string) => { res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' }); res.write(msg || 'Not Found App\n'); res.end(); @@ -208,27 +209,11 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR return createNotFoundPage('Invalid proxy url'); } console.log('proxyUrl', appFileUrl, proxyUrl); - let protocol = proxyUrl.startsWith('https') ? https : http; - // 代理 - const proxyReq = protocol.request(proxyUrl, (proxyRes) => { - res.writeHead(proxyRes.statusCode, { - ...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(); - } - proxyRes.pipe(res, { end: true }); + httpProxy(req, res, { + proxyUrl, + userApp, + createNotFoundPage, }); - proxyReq.on('error', (err) => { - console.error(`Proxy request error: ${err.message}`); - userApp.clearCacheData(); - }); - proxyReq.end(); // userApp.clearCacheData() return; } diff --git a/src/module/minio.ts b/src/module/minio.ts index 5f871ec..7fb1724 100644 --- a/src/module/minio.ts +++ b/src/module/minio.ts @@ -5,8 +5,10 @@ type MinioConfig = { minio: ClientOptions & { bucketName: string }; }; const config = useConfig(); - const { bucketName, ...minioRest } = config.minio; +const { port, endPoint, useSSL } = minioRest; +export const minioUrl = `http${useSSL ? 's' : ''}://${endPoint}:${port || 9000}`; +export const minioResources = `${minioUrl}/resources`; export const minioClient = new Client(minioRest); export { bucketName }; if (!minioClient) { diff --git a/src/module/proxy/http-proxy.ts b/src/module/proxy/http-proxy.ts new file mode 100644 index 0000000..08275e6 --- /dev/null +++ b/src/module/proxy/http-proxy.ts @@ -0,0 +1,89 @@ +import { pipeline } from 'stream'; +import { promisify } from 'util'; +import { bucketName, minioClient, minioResources } from '../minio.ts'; +import fs from 'fs'; +import { IncomingMessage, ServerResponse } from 'http'; +import http from 'http'; +import https from 'https'; +import { UserApp } from '../get-user-app.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 async function minioProxy( + req: IncomingMessage, + res: ServerResponse, + opts: { + proxyUrl: string; + userApp: UserApp; + createNotFoundPage: (msg?: string) => any; + }, +) { + const fileUrl = opts.proxyUrl; + const { userApp, createNotFoundPage } = opts; + const objectName = fileUrl.replace(minioResources + '/', ''); + try { + const stat = await minioClient.statObject(bucketName, objectName); + if (stat.size === 0) { + return createNotFoundPage('Invalid proxy url'); + } + const contentLength = stat.size; + const etag = stat.etag; + const lastModified = stat.lastModified.toISOString(); + // console.log('contentType', stat.metaData); + res.writeHead(200, { + 'Content-Length': contentLength, + etag, + 'last-modified': lastModified, + ...stat.metaData, + }); + const objectStream = await minioClient.getObject(bucketName, objectName); + objectStream.pipe(res, { end: true }); + } catch (error) { + console.error(`Proxy request error: ${error.message}`); + userApp.clearCacheData(); + return createNotFoundPage('Invalid proxy url'); + } +} + +export const httpProxy = async ( + req: IncomingMessage, + res: ServerResponse, + opts: { + proxyUrl: string; + userApp: UserApp; + createNotFoundPage: (msg?: string) => any; + }, +) => { + const { proxyUrl, userApp, createNotFoundPage } = opts; + if (proxyUrl.startsWith(minioResources)) { + return minioProxy(req, res, opts); + } + let protocol = proxyUrl.startsWith('https') ? https : http; + // 代理 + const proxyReq = protocol.request(proxyUrl, (proxyRes) => { + res.writeHead(proxyRes.statusCode, { + ...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(); + } + proxyRes.pipe(res, { end: true }); + }); + proxyReq.on('error', (err) => { + console.error(`Proxy request error: ${err.message}`); + userApp.clearCacheData(); + }); + proxyReq.end(); +}; diff --git a/src/route/app/list.ts b/src/route/app/list.ts index 6c5e9f6..16d5654 100644 --- a/src/route/app/list.ts +++ b/src/route/app/list.ts @@ -8,6 +8,7 @@ app .route({ path: 'app', key: 'auth-admin', + id: 'auth-admin', }) .define(async (ctx) => { const { user } = ctx.query; @@ -116,7 +117,7 @@ app .route({ path: 'app', key: 'status', - middleware: ['auth-admin'], + middleware: [], }) .define(async (ctx) => { const { user, app } = ctx.query;