add ai proxy

This commit is contained in:
熊潇 2025-05-08 00:12:14 +08:00
parent b4fc1a545c
commit f2cc76a8ea
5 changed files with 137 additions and 53 deletions

View File

@ -7,8 +7,8 @@ import { nanoid } from 'nanoid';
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 { getAppLoadStatus, setAppLoadStatus } from './redis/get-app-status.ts';
import { minioResources } from './minio.ts';
import { downloadFileFromMinio } from './proxy/http-proxy.ts';
const pipelineAsync = promisify(pipeline);

View File

@ -11,11 +11,48 @@ import { getTextFromStreamAndAddStat, httpProxy } from './proxy/http-proxy.ts';
import { UserPermission } from '@kevisual/permission';
import { getLoginUser } from '@/middleware/auth.ts';
import { rediretHome } from './user-home/index.ts';
import { aiProxy } from './proxy/ai-proxy.ts';
const api = config?.api || { host: 'kevisual.xiongxiao.me', path: '/api/router' };
const domain = config?.proxy?.domain || 'kevisual.xiongxiao.me';
const allowedOrigins = config?.proxy?.allowedOrigin || [];
const noProxyUrl = ['/', '/favicon.ico'];
const notAuthPathList = [
{
user: 'root',
paths: ['center'],
},
{
user: 'admin',
paths: ['center'],
},
{
user: 'user',
paths: ['login'],
},
{
user: 'public',
paths: ['center'],
all: true,
},
{
user: 'test',
paths: ['center'],
all: true,
},
];
const checkNotAuthPath = (user, app) => {
const notAuthPath = notAuthPathList.find((item) => {
if (item.user === user) {
if (item.all) {
return true;
}
return item.paths?.includes?.(app);
}
return false;
});
return notAuthPath;
};
export const handleRequest = async (req: http.IncomingMessage, res: http.ServerResponse) => {
const querySearch = new URL(req.url, `http://${req.headers.host}`).searchParams;
const password = querySearch.get('p');
@ -176,10 +213,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
user = _user;
app = _app;
}
const userApp = new UserApp({ user, app });
let isExist = await userApp.getExist();
const createRefreshPage = () => {
const createRefreshPage = (user, app) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(createRefreshHtml(user, app));
};
@ -193,11 +227,20 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
res.write(msg || 'Not Found App\n');
res.end();
};
if (app === 'ai') {
return aiProxy(req, res, {
createNotFoundPage,
});
}
const userApp = new UserApp({ user, app });
let isExist = await userApp.getExist();
if (!isExist) {
try {
const { code, loading, message } = await userApp.setCacheData();
if (loading || code === 20000) {
return createRefreshPage();
return createRefreshPage(user, app);
} else if (code === 500) {
return createNotFoundPage(message || 'Not Found App\n');
} else if (code !== 200) {
@ -214,42 +257,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
if (!isExist) {
return createNotFoundPage();
}
const notAuthPathList = [
{
user: 'root',
paths: ['center'],
},
{
user: 'admin',
paths: ['center'],
},
{
user: 'user',
paths: ['login'],
},
{
user: 'public',
paths: ['center'],
all: true,
},
{
user: 'test',
paths: ['center'],
all: true,
},
];
const checkNotAuthPath = (user, app) => {
const notAuthPath = notAuthPathList.find((item) => {
if (item.user === user) {
if (item.all) {
return true;
}
return item.paths?.includes?.(app);
}
return false;
});
return notAuthPath;
};
if (!checkNotAuthPath(user, app)) {
const { permission } = isExist;
const permissionInstance = new UserPermission({ permission, owner: user });
@ -283,7 +291,6 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
userApp,
createNotFoundPage,
});
// userApp.clearCacheData()
return;
}
console.log('appFile', appFile, appFileUrl, isExist);
@ -295,9 +302,6 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
const isHTML = contentType.includes('html');
const filePath = path.join(fileStore, indexFilePath);
if (!userApp.fileCheck(filePath)) {
// 动态删除文件
// res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
// res.write('App Cache expired, Please refresh\n');
res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8', tips: 'App Cache expired, Please refresh' });
res.write(createRefreshHtml(user, app));
res.end();

View File

@ -0,0 +1,64 @@
import { bucketName, minioClient } from '../minio.ts';
import { IncomingMessage, ServerResponse } from 'http';
import { filterKeys } from './http-proxy.ts';
import { getUserFromRequest } from '@/utils/get-user.ts';
import { UserPermission, Permission } from '@kevisual/permission';
import { getLoginUser } from '@/middleware/auth.ts';
export const aiProxy = async (
req: IncomingMessage,
res: ServerResponse,
opts: {
createNotFoundPage: (msg?: string) => any;
},
) => {
const { createNotFoundPage } = opts;
const _u = new URL(req.url, 'http://localhost');
const pathname = _u.pathname;
const params = _u.searchParams;
const password = params.get('p');
const version = params.get('version') || '1.0.0';
const { user, app } = getUserFromRequest(req);
const objectName = pathname.replace(`/${user}/${app}/`, `${user}/${app}/${version}/`);
try {
const stat = await minioClient.statObject(bucketName, objectName);
if (stat.size === 0) {
createNotFoundPage('Invalid proxy url');
return true;
}
const permissionInstance = new UserPermission({ permission: stat.metaData as Permission, owner: user });
const loginUser = await getLoginUser(req);
const checkPermission = permissionInstance.checkPermissionSuccess({
username: loginUser?.tokenUser?.username || '',
password: password,
});
if (!checkPermission.success) {
return createNotFoundPage('no permission');
}
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 objectStream = await minioClient.getObject(bucketName, objectName);
const headers = {
'Content-Length': contentLength,
etag,
'last-modified': lastModified,
'file-name': fileName,
...filterMetaData,
};
res.writeHead(200, {
...headers,
});
objectStream.pipe(res, { end: true });
return true;
} catch (error) {
console.error(`Proxy request error: ${error.message}`);
createNotFoundPage('Invalid ai proxy url');
return false;
}
};

View File

@ -32,18 +32,18 @@ export async function minioProxy(
res: ServerResponse,
opts: {
proxyUrl: string;
userApp: UserApp;
createNotFoundPage: (msg?: string) => any;
isDownload?: boolean;
},
) {
const fileUrl = opts.proxyUrl;
const { userApp, createNotFoundPage, isDownload = false } = opts;
const { createNotFoundPage, isDownload = false } = opts;
const objectName = fileUrl.replace(minioResources + '/', '');
try {
const stat = await minioClient.statObject(bucketName, objectName);
if (stat.size === 0) {
return createNotFoundPage('Invalid proxy url');
createNotFoundPage('Invalid proxy url');
return true;
}
const filterMetaData = filterKeys(stat.metaData, ['size', 'etag', 'last-modified']);
const contentLength = stat.size;
@ -72,10 +72,11 @@ export async function minioProxy(
});
objectStream.pipe(res, { end: true });
}
return true;
} catch (error) {
console.error(`Proxy request error: ${error.message}`);
userApp.clearCacheData();
return createNotFoundPage('Invalid proxy url');
createNotFoundPage('Invalid proxy url');
return false;
}
}
@ -114,7 +115,11 @@ export const httpProxy = async (
const params = _u.searchParams;
const isDownload = params.get('download') === 'true';
if (proxyUrl.startsWith(minioResources)) {
return minioProxy(req, res, { ...opts, isDownload });
const isOk = await minioProxy(req, res, { ...opts, isDownload });
if (!isOk) {
userApp.clearCacheData();
}
return;
}
let protocol = proxyUrl.startsWith('https') ? https : http;
// 代理

11
src/utils/get-user.ts Normal file
View File

@ -0,0 +1,11 @@
import { IncomingMessage, ServerResponse } from 'http';
export const getUserFromRequest = (req: IncomingMessage) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
const keys = pathname.split('/');
const [_, user, app] = keys;
return {
user,
app,
};
};