feat: add ai proxy

This commit is contained in:
熊潇 2025-05-23 11:09:54 +08:00
parent 28a5d82e52
commit 4d9df0fee3
6 changed files with 40 additions and 24 deletions

View File

@ -1,6 +1,7 @@
import { User } from '@/module/models.ts'; import { User } from '@/module/models.ts';
import http from 'http'; import http from 'http';
import cookie from 'cookie'; import cookie from 'cookie';
import { logger } from '@/module/logger.ts';
export const error = (msg: string, code = 500) => { export const error = (msg: string, code = 500) => {
return JSON.stringify({ code, message: msg }); return JSON.stringify({ code, message: msg });
}; };
@ -55,6 +56,7 @@ export const getLoginUser = async (req: http.IncomingMessage) => {
return null; return null;
} }
let tokenUser; let tokenUser;
logger.debug('getLoginUser', token);
try { try {
tokenUser = await User.verifyToken(token); tokenUser = await User.verifyToken(token);
return { tokenUser, token }; return { tokenUser, token };

View File

@ -360,7 +360,7 @@ export const handleRequest = async (req: http.IncomingMessage, res: http.ServerR
headers.set('Content-Type', contentType); headers.set('Content-Type', contentType);
headers.set('Cache-Control', isHTML ? 'no-cache' : 'public, max-age=3600'); // 设置缓存时间为 1 小时 headers.set('Cache-Control', isHTML ? 'no-cache' : 'public, max-age=3600'); // 设置缓存时间为 1 小时
headers.set('ETag', eTag); headers.set('ETag', eTag);
res.setHeaders(headers); res?.setHeaders?.(headers);
if (isHTML) { if (isHTML) {
const newHtml = await getTextFromStreamAndAddStat(fs.createReadStream(filePath)); const newHtml = await getTextFromStreamAndAddStat(fs.createReadStream(filePath));
resContent = newHtml.html; resContent = newHtml.html;

View File

@ -25,7 +25,7 @@ const getAiProxy = async (req: IncomingMessage, res: ServerResponse, opts: Proxy
try { try {
if (dir) { if (dir) {
if (!isOwner) { if (!isOwner) {
return createNotFoundPage('no permission'); return createNotFoundPage('no dir permission');
} }
const list = await oss.listObjects<true>(objectName, { recursive: recursive }); const list = await oss.listObjects<true>(objectName, { recursive: recursive });
res.writeHead(200, { 'content-type': 'application/json' }); res.writeHead(200, { 'content-type': 'application/json' });
@ -63,6 +63,7 @@ const getAiProxy = async (req: IncomingMessage, res: ServerResponse, opts: Proxy
password: password, password: password,
}); });
if (!checkPermission.success) { if (!checkPermission.success) {
logger.info('no permission', checkPermission, loginUser, owner);
return createNotFoundPage('no permission'); return createNotFoundPage('no permission');
} }
if (hash && stat.etag === hash) { if (hash && stat.etag === hash) {
@ -202,15 +203,15 @@ export const getObjectName = async (req: IncomingMessage, opts?: { checkOwner?:
if (app === 'ai') { if (app === 'ai') {
const version = params.get('version') || '1.0.0'; // root/ai const version = params.get('version') || '1.0.0'; // root/ai
objectName = pathname.replace(`/${user}/${app}/`, `${user}/${app}/${version}/`); objectName = pathname.replace(`/${user}/${app}/`, `${user}/${app}/${version}/`);
owner = user;
} else { } else {
objectName = pathname.replace(`/${user}/${app}/`, `${user}/`); // resources/root/ objectName = pathname.replace(`/${user}/${app}/`, `${user}/`); // resources/root/
owner = user;
} }
owner = user;
let isOwner = undefined; let isOwner = undefined;
let loginUser: Awaited<ReturnType<typeof getLoginUser>> = null; let loginUser: Awaited<ReturnType<typeof getLoginUser>> = null;
if (checkOwner) { if (checkOwner) {
const loginUser = await getLoginUser(req); loginUser = await getLoginUser(req);
logger.debug('getObjectName', loginUser, user, app);
isOwner = loginUser?.tokenUser?.username === owner; isOwner = loginUser?.tokenUser?.username === owner;
} }
return { return {

View File

@ -2,6 +2,7 @@ import { WebSocketServer } from 'ws';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { WsProxyManager } from './manager.ts'; import { WsProxyManager } from './manager.ts';
import { getLoginUser } from '@/middleware/auth.ts'; import { getLoginUser } from '@/middleware/auth.ts';
import { logger } from '../logger.ts';
export const wsProxyManager = new WsProxyManager(); export const wsProxyManager = new WsProxyManager();
export const upgrade = async (request: any, socket: any, head: any) => { export const upgrade = async (request: any, socket: any, head: any) => {
@ -26,24 +27,20 @@ export const wss = new WebSocketServer({
wss.on('connection', async (ws, req) => { wss.on('connection', async (ws, req) => {
console.log('connected', req.url); console.log('connected', req.url);
// const user = await getLoginUser(req);
// if (!user) {
// ws.send(
// JSON.stringify({
// type: 'error',
// message: 'Invalid authorization',
// }),
// );
// ws.close();
// return;
// }
const url = new URL(req.url, 'http://localhost'); const url = new URL(req.url, 'http://localhost');
const id = url?.searchParams?.get('id') || nanoid(); const id = url?.searchParams?.get('id') || nanoid();
const user = 'root'; const loginUser = await getLoginUser(req);
if (!loginUser) {
ws.send(JSON.stringify({ code: 401, message: 'No Login' }));
ws.close();
return;
}
const user = loginUser.tokenUser?.username;
wsProxyManager.register(id, { user, ws }); wsProxyManager.register(id, { user, ws });
ws.send( ws.send(
JSON.stringify({ JSON.stringify({
type: 'connected', type: 'connected',
user: user,
id, id,
}), }),
); );
@ -53,10 +50,10 @@ wss.on('connection', async (ws, req) => {
return; return;
} }
const data = JSON.parse(eventData); const data = JSON.parse(eventData);
console.log('message', data); logger.debug('message', data);
}); });
ws.on('close', () => { ws.on('close', () => {
console.log('ws closed'); logger.debug('ws closed');
wsProxyManager.unregister(id, user); wsProxyManager.unregister(id, user);
}); });
}); });

View File

@ -2,8 +2,8 @@ import { IncomingMessage, ServerResponse } from 'http';
import { wsProxyManager } from './index.ts'; import { wsProxyManager } from './index.ts';
import { App } from '@kevisual/router'; import { App } from '@kevisual/router';
import { log } from 'console';
import { logger } from '../logger.ts'; import { logger } from '../logger.ts';
import { getLoginUser } from '@/middleware/auth.ts';
type ProxyOptions = { type ProxyOptions = {
createNotFoundPage: (msg?: string) => any; createNotFoundPage: (msg?: string) => any;
@ -13,15 +13,24 @@ export const UserV1Proxy = async (req: IncomingMessage, res: ServerResponse, opt
const { pathname } = new URL(url || '', `http://localhost`); const { pathname } = new URL(url || '', `http://localhost`);
const [user, app, userAppKey] = pathname.split('/').slice(1); const [user, app, userAppKey] = pathname.split('/').slice(1);
if (!user || !app || !userAppKey) { if (!user || !app || !userAppKey) {
opts?.createNotFoundPage?.('应用未启动'); opts?.createNotFoundPage?.('应用未找到');
return false; return false;
} }
const data = await App.handleRequest(req, res); const data = await App.handleRequest(req, res);
const loginUser = await getLoginUser(req);
if (!loginUser) {
opts?.createNotFoundPage?.('没有登录');
return false;
}
if (loginUser.tokenUser?.username !== user) {
opts?.createNotFoundPage?.('没有访问应用权限');
return false;
}
logger.debug('data', data); logger.debug('data', data);
const client = wsProxyManager.get(userAppKey, user); const client = wsProxyManager.get(userAppKey, user);
const ids = wsProxyManager.getIds(); const ids = wsProxyManager.getIds();
if (!client) { if (!client) {
opts?.createNotFoundPage?.(`应用未启动, 未找到应用, ${userAppKey}, ${ids.join(',')}`); opts?.createNotFoundPage?.(`未找到应用, ${userAppKey}, ${ids.join(',')}`);
return false; return false;
} }
const value = await client.sendData(data); const value = await client.sendData(data);

View File

@ -4,6 +4,7 @@ import { redis } from '@/module/redis/redis.ts';
import fs from 'fs'; import fs from 'fs';
import { fileStore } from '../../module/config.ts'; import { fileStore } from '../../module/config.ts';
import { getAppLoadStatus } from '@/module/redis/get-app-status.ts'; import { getAppLoadStatus } from '@/module/redis/get-app-status.ts';
import { getLoginUser } from '@/middleware/auth.ts';
export class CenterUserApp { export class CenterUserApp {
user: string; user: string;
@ -63,9 +64,15 @@ app
}) })
.define(async (ctx) => { .define(async (ctx) => {
const { user } = ctx.query; const { user } = ctx.query;
if (user !== 'admin') { const loginUser = await getLoginUser(ctx.req);
ctx.throw('Not Found'); if (loginUser) {
const root = ['admin', 'root'];
if (root.includes(loginUser.tokenUser?.username)) {
return;
} }
ctx.throw(401, 'No Proxy App Permission');
}
ctx.throw(401, 'No Login And No Proxy App Permission');
}) })
.addTo(app); .addTo(app);