mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
10
projects/sandbox/src/app.module.ts
Normal file
10
projects/sandbox/src/app.module.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SandboxController } from './sandbox/sandbox.controller';
|
||||
import { SandboxService } from './sandbox/sandbox.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [SandboxController],
|
||||
providers: [SandboxService]
|
||||
})
|
||||
export class AppModule {}
|
18
projects/sandbox/src/http-exception.filter.ts
Normal file
18
projects/sandbox/src/http-exception.filter.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { getErrText } from './utils';
|
||||
|
||||
@Catch()
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
catch(error: any, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<FastifyReply>();
|
||||
const request = ctx.getRequest<FastifyRequest>();
|
||||
|
||||
response.status(500).send({
|
||||
success: false,
|
||||
time: new Date(),
|
||||
msg: getErrText(error)
|
||||
});
|
||||
}
|
||||
}
|
38
projects/sandbox/src/main.ts
Normal file
38
projects/sandbox/src/main.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
|
||||
import { AppModule } from './app.module';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
import { HttpExceptionFilter } from './http-exception.filter';
|
||||
import { ResponseInterceptor } from './response';
|
||||
|
||||
async function bootstrap(port: number) {
|
||||
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());
|
||||
|
||||
// 使用全局异常过滤器
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
|
||||
app.useGlobalInterceptors(new ResponseInterceptor());
|
||||
|
||||
const config = new DocumentBuilder()
|
||||
.setTitle('Cats example')
|
||||
.setDescription('The cats API description')
|
||||
.setVersion('1.0')
|
||||
.addTag('cats')
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
||||
try {
|
||||
await app.listen(port, '0.0.0.0');
|
||||
console.log(`Application is running on: ${await app.getUrl()}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
console.warn(`Port ${port} is already in use, trying next port...`);
|
||||
await bootstrap(port + 1);
|
||||
} else {
|
||||
console.error(`Failed to start application: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
bootstrap(3000);
|
15
projects/sandbox/src/response.ts
Normal file
15
projects/sandbox/src/response.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class ResponseInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
return next.handle().pipe(
|
||||
map((data) => ({
|
||||
success: true,
|
||||
data
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
4
projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts
Normal file
4
projects/sandbox/src/sandbox/dto/create-sandbox.dto.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export class RunCodeDto {
|
||||
code: string;
|
||||
variables: object;
|
||||
}
|
20
projects/sandbox/src/sandbox/sandbox.controller.spec.ts
Normal file
20
projects/sandbox/src/sandbox/sandbox.controller.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SandboxController } from './sandbox.controller';
|
||||
import { SandboxService } from './sandbox.service';
|
||||
|
||||
describe('SandboxController', () => {
|
||||
let controller: SandboxController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [SandboxController],
|
||||
providers: [SandboxService]
|
||||
}).compile();
|
||||
|
||||
controller = module.get<SandboxController>(SandboxController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
});
|
15
projects/sandbox/src/sandbox/sandbox.controller.ts
Normal file
15
projects/sandbox/src/sandbox/sandbox.controller.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
||||
import { SandboxService } from './sandbox.service';
|
||||
import { RunCodeDto } from './dto/create-sandbox.dto';
|
||||
import { WorkerNameEnum, runWorker } from 'src/worker/utils';
|
||||
|
||||
@Controller('sandbox')
|
||||
export class SandboxController {
|
||||
constructor(private readonly sandboxService: SandboxService) {}
|
||||
|
||||
@Post('/js')
|
||||
@HttpCode(200)
|
||||
runJs(@Body() codeProps: RunCodeDto) {
|
||||
return runWorker(WorkerNameEnum.runJs, codeProps);
|
||||
}
|
||||
}
|
9
projects/sandbox/src/sandbox/sandbox.module.ts
Normal file
9
projects/sandbox/src/sandbox/sandbox.module.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SandboxService } from './sandbox.service';
|
||||
import { SandboxController } from './sandbox.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [SandboxController],
|
||||
providers: [SandboxService]
|
||||
})
|
||||
export class SandboxModule {}
|
18
projects/sandbox/src/sandbox/sandbox.service.spec.ts
Normal file
18
projects/sandbox/src/sandbox/sandbox.service.spec.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SandboxService } from './sandbox.service';
|
||||
|
||||
describe('SandboxService', () => {
|
||||
let service: SandboxService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [SandboxService]
|
||||
}).compile();
|
||||
|
||||
service = module.get<SandboxService>(SandboxService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
10
projects/sandbox/src/sandbox/sandbox.service.ts
Normal file
10
projects/sandbox/src/sandbox/sandbox.service.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { RunCodeDto } from './dto/create-sandbox.dto';
|
||||
import { WorkerNameEnum, runWorker } from 'src/worker/utils';
|
||||
|
||||
@Injectable()
|
||||
export class SandboxService {
|
||||
runJs(params: RunCodeDto) {
|
||||
return runWorker(WorkerNameEnum.runJs, params);
|
||||
}
|
||||
}
|
14
projects/sandbox/src/utils.ts
Normal file
14
projects/sandbox/src/utils.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const replaceSensitiveText = (text: string) => {
|
||||
// 1. http link
|
||||
text = text.replace(/(?<=https?:\/\/)[^\s]+/g, 'xxx');
|
||||
// 2. nx-xxx 全部替换成xxx
|
||||
text = text.replace(/ns-[\w-]+/g, 'xxx');
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
export const getErrText = (err: any, def = '') => {
|
||||
const msg: string = typeof err === 'string' ? err : err?.message ?? def;
|
||||
msg && console.log('error =>', msg);
|
||||
return replaceSensitiveText(msg);
|
||||
};
|
38
projects/sandbox/src/worker/runJs.ts
Normal file
38
projects/sandbox/src/worker/runJs.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { RunCodeDto } from 'src/sandbox/dto/create-sandbox.dto';
|
||||
import { parentPort } from 'worker_threads';
|
||||
import { workerResponse } from './utils';
|
||||
|
||||
// @ts-ignore
|
||||
const ivm = require('isolated-vm');
|
||||
|
||||
parentPort?.on('message', ({ code, variables = {} }: RunCodeDto) => {
|
||||
const resolve = (data: any) => workerResponse({ parentPort, type: 'success', data });
|
||||
const reject = (error: any) => workerResponse({ parentPort, type: 'error', data: error });
|
||||
|
||||
const isolate = new ivm.Isolate({ memoryLimit: 32 });
|
||||
const context = isolate.createContextSync();
|
||||
const jail = context.global;
|
||||
|
||||
// custom log function
|
||||
jail.setSync('responseData', function (args: any): any {
|
||||
if (typeof args === 'object') {
|
||||
resolve(args);
|
||||
} else {
|
||||
reject('Not an invalid response');
|
||||
}
|
||||
});
|
||||
|
||||
// Add global variables
|
||||
jail.setSync('variables', new ivm.ExternalCopy(variables).copyInto());
|
||||
|
||||
try {
|
||||
const scriptCode = `
|
||||
${code}
|
||||
responseData(main(variables))`;
|
||||
context.evalSync(scriptCode, { timeout: 6000 });
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
47
projects/sandbox/src/worker/utils.ts
Normal file
47
projects/sandbox/src/worker/utils.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { type MessagePort, Worker } from 'worker_threads';
|
||||
import * as path from 'path';
|
||||
|
||||
export enum WorkerNameEnum {
|
||||
runJs = 'runJs',
|
||||
runPy = 'runPy'
|
||||
}
|
||||
|
||||
type WorkerResponseType = { type: 'success' | 'error'; data: any };
|
||||
|
||||
export const getWorker = (name: WorkerNameEnum) => {
|
||||
const baseUrl =
|
||||
process.env.NODE_ENV === 'production' ? 'projects/sandbox/dist/worker' : 'dist/worker';
|
||||
const workerPath = path.join(process.cwd(), baseUrl, `${name}.js`);
|
||||
return new Worker(workerPath);
|
||||
};
|
||||
|
||||
export const runWorker = <T = any>(name: WorkerNameEnum, params?: Record<string, any>) => {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const worker = getWorker(name);
|
||||
|
||||
worker.postMessage(params);
|
||||
|
||||
worker.on('message', (msg: WorkerResponseType) => {
|
||||
if (msg.type === 'error') return reject(msg.data);
|
||||
|
||||
resolve(msg.data);
|
||||
worker.terminate();
|
||||
});
|
||||
|
||||
worker.on('error', (err) => {
|
||||
reject(err);
|
||||
worker.terminate();
|
||||
});
|
||||
worker.on('messageerror', (err) => {
|
||||
reject(err);
|
||||
worker.terminate();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const workerResponse = ({
|
||||
parentPort,
|
||||
...data
|
||||
}: WorkerResponseType & { parentPort?: MessagePort }) => {
|
||||
parentPort?.postMessage(data);
|
||||
};
|
Reference in New Issue
Block a user