当前位置: 首页 > news >正文

自建node云函数服务器

最近想自己搭建一个云函数服务器,大概就是可以自己编写一些云函数,实现动态加载这样子,理论是这样的,打算启动一个Node进程,搭建一个HTTP服务,固定路径/api/function ,参数:

   { functionName:"文件名",methodName:'方法名称', params:{'参数1'} }

接收到请求的时候,根据functionName 去拼接文件名,并用import 导入成一个对象,最后调用对应的方法和参数,在这里我还加了一个缓存,避免短时间调用时不走重复加载,代码如下:

// 导入Node.js的http模块
import http from 'http';
import url from 'url';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';// 获取当前文件的目录路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);// 创建HTTP服务器对象
const funServer = {server: null,port: 3000,routes: {}, // 存储路由functionsDir: path.join(__dirname, 'functions'), // 云函数目录functionCache: {}, // 云函数缓存cacheTimeout: 60000, // 缓存过期时间(毫秒)// 初始化服务器init() {this.server = http.createServer(this.handleRequest.bind(this));this.setupRoutes();this.initFunctionsDir();this.startCacheCleanup();},// 启动缓存清理定时器startCacheCleanup() {// 每60秒检查一次缓存,避免太频繁清理setInterval(() => {this.cleanupCache();}, 60000);console.log('缓存清理定时器已启动,每60秒运行一次');},// 清理过期缓存cleanupCache() {const now = Date.now();const cacheKeys = Object.keys(this.functionCache);let clearedCount = 0;console.log(`开始缓存清理: 当前缓存数量 ${cacheKeys.length}`);cacheKeys.forEach(key => {const cacheItem = this.functionCache[key];if (cacheItem) {// 基于最后访问时间进行过期检查if (now - cacheItem.lastAccessTime > this.cacheTimeout) {delete this.functionCache[key];console.log(`缓存已过期并清除: ${key}, 最后访问时间: ${new Date(cacheItem.lastAccessTime).toISOString()}, 当前时间: ${new Date(now).toISOString()}, 超过${this.cacheTimeout/1000}秒未访问`);clearedCount++;}}});console.log(`缓存清理完成: 清理了 ${clearedCount} 个过期缓存,剩余缓存数量 ${Object.keys(this.functionCache).length}`);},// 从缓存获取模块getCachedModule(modulePath) {const cacheItem = this.functionCache[modulePath];if (cacheItem) {const now = Date.now();console.log(`检查缓存: ${modulePath}, 最后访问时间: ${new Date(cacheItem.lastAccessTime).toISOString()}, 当前时间: ${new Date(now).toISOString()}, 间隔: ${now - cacheItem.lastAccessTime}ms`);// 检查是否过期(基于最后访问时间)if (now - cacheItem.lastAccessTime <= this.cacheTimeout) {// 更新最后访问时间cacheItem.lastAccessTime = now;console.log(`使用缓存: ${modulePath}, 已更新最后访问时间`);return cacheItem.module;}// 如果过期了,删除缓存delete this.functionCache[modulePath];console.log(`缓存已过期: ${modulePath}, 最后访问时间: ${new Date(cacheItem.lastAccessTime).toISOString()}, 超过${this.cacheTimeout/1000}秒未访问`);}return null;},// 缓存模块cacheModule(modulePath, module) {const now = Date.now();this.functionCache[modulePath] = {module,timestamp: now, // 首次创建时间lastAccessTime: now // 最后访问时间};console.log(`模块已缓存: ${modulePath}, 创建时间: ${new Date(now).toISOString()}`);},// 清除指定函数的缓存clearFunctionCache(functionName) {const modulePath = `file://${path.join(this.functionsDir, `${functionName}.js`)}`;if (this.functionCache[modulePath]) {delete this.functionCache[modulePath];console.log(`清除缓存: ${modulePath}`);return true;}return false;},// 清除所有缓存clearAllCache() {const cacheCount = Object.keys(this.functionCache).length;this.functionCache = {};console.log(`已清除所有缓存,共${cacheCount}个模块`);return cacheCount;},// 设置路由setupRoutes() {// 根路径this.routes['GET /'] = this.handleRoot;// API路径this.routes['GET /api/status'] = this.handleStatus;this.routes['GET /api/data'] = this.handleData;// 云函数执行路径this.routes['POST /api/function'] = this.handleFunctionExecution;// 云函数重新加载路径this.routes['POST /api/reload-function'] = this.handleReloadFunction;// 清除所有缓存路径this.routes['POST /api/clear-cache'] = this.handleClearCache;// 404处理this.routes['notFound'] = this.handleNotFound;},// 初始化云函数目录initFunctionsDir() {if (!fs.existsSync(this.functionsDir)) {fs.mkdirSync(this.functionsDir, { recursive: true });console.log(`创建云函数目录: ${this.functionsDir}`);}},// 启动服务器start() {this.server.listen(this.port, () => {console.log(`服务器已启动,监听端口 ${this.port}`);console.log(`访问地址: http://localhost:${this.port}`);console.log(`API状态: http://localhost:${this.port}/api/status`);console.log(`API数据: http://localhost:${this.port}/api/data`);console.log(`云函数执行: http://localhost:${this.port}/api/function (POST)`);console.log(`云函数重载: http://localhost:${this.port}/api/reload-function (POST)`);console.log(`清除缓存: http://localhost:${this.port}/api/clear-cache (POST)`);console.log(`云函数目录: ${this.functionsDir}`);console.log(`云函数缓存过期时间: ${this.cacheTimeout / 1000}秒(基于最后访问时间)`);});},// 处理请求的方法handleRequest(req, res) {try {const parsedUrl = url.parse(req.url, true);const path = parsedUrl.pathname;const method = req.method;console.log(`收到请求: ${method} ${path}`);// 构建路由键const routeKey = `${method} ${path}`;// 查找对应的处理函数const handler = this.routes[routeKey] || this.routes['notFound'];// 对于需要解析请求体的POST请求if (method === 'POST' && (path === '/api/function' || path === '/api/reload-function' || path === '/api/clear-cache')) {let body = '';req.on('data', chunk => {body += chunk.toString();});req.on('end', () => {try {const bodyData = body ? JSON.parse(body) : {};handler.call(this, req, res, parsedUrl, bodyData);} catch (parseError) {this.sendJSON(res, { error: '无效的JSON格式' }, 400);}});} else {// 传递请求和响应对象给处理函数handler.call(this, req, res, parsedUrl);}} catch (error) {this.handleError(req, res, error);}},// 根路径处理handleRoot(req, res) {res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});const html = `<!DOCTYPE html><html><head><title>Node.js HTTP服务器 - 云函数平台</title><style>body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }h1 { color: #333; }.api-section { margin-top: 30px; }h2 { color: #555; }.api-links { margin-top: 20px; }.api-links a { margin-right: 20px; color: #0066cc; text-decoration: none; }.api-links a:hover { text-decoration: underline; }.code-block { background: #f5f5f5; padding: 15px; border-radius: 5px; margin: 15px 0; font-family: monospace; }.note { background: #e8f4f8; padding: 10px; border-left: 4px solid #0066cc; }</style></head><body><h1>Hello World!</h1><p>这是一个使用Node.js创建的HTTP服务器,支持云函数执行功能。</p><div class="api-section"><h2>API端点</h2><div class="api-links"><a href="/api/status">API状态</a><a href="/api/data">API数据</a></div></div><div class="api-section"><h2>云函数执行</h2><p>使用POST请求到 <code>/api/function</code> 来执行云函数</p><div class="code-block"><strong>请求格式:</strong><br>POST /api/function<br>Content-Type: application/json<br><br>{"functionName": "函数文件名","methodName": "方法名","params": {}}</div><div class="note"><strong>注意:</strong> 云函数文件需要放在 <code>functions</code> 目录下,并且导出相应的方法。</div></div></body></html>`;res.end(html);},// API状态处理handleStatus(req, res) {this.sendJSON(res, {status: 'ok',message: '服务器运行正常',timestamp: new Date().toISOString()});},// API数据处理handleData(req, res) {this.sendJSON(res, {data: [{ id: 1, name: '项目1', value: Math.random() * 100 },{ id: 2, name: '项目2', value: Math.random() * 100 },{ id: 3, name: '项目3', value: Math.random() * 100 }],count: 3,timestamp: new Date().toISOString()});},// 404处理handleNotFound(req, res) {res.writeHead(404, {'Content-Type': 'text/plain; charset=utf-8'});res.end('404 - 页面未找到');},// 错误处理handleError(req, res, error) {console.error('服务器错误:', error);res.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8'});res.end('500 - 服务器内部错误');},// 处理云函数执行async handleFunctionExecution(req, res, parsedUrl, bodyData) {try {const { functionName, methodName, params = {} } = bodyData;// 参数验证if (!functionName || !methodName) {return this.sendJSON(res, { error: '缺少必要参数: functionName 和 methodName' }, 400);}// 安全检查 - 防止路径遍历攻击if (functionName.includes('../') || functionName.includes('..\\')) {return this.sendJSON(res, { error: '不允许的函数名' }, 403);}const functionPath = path.join(this.functionsDir, `${functionName}.js`);// 检查文件是否存在if (!fs.existsSync(functionPath)) {return this.sendJSON(res, { error: `函数文件不存在: ${functionName}.js` }, 404);}console.log(`执行云函数: ${functionName}.${methodName}`);// 动态导入函数模块const modulePath = `file://${functionPath}`;let module;let fromCache = false;// 先尝试从缓存获取模块module = this.getCachedModule(modulePath);if (module) {fromCache = true;console.log(`从缓存加载模块: ${functionName}`);} else {// 在ESM中,我们不使用require.cache,而是使用时间戳来强制重新加载console.log(`首次加载模块: ${functionName}`);module = await import(modulePath + '?' + Date.now()); // 添加时间戳确保重新加载this.cacheModule(modulePath, module);}// 检查方法是否存在if (typeof module[methodName] !== 'function') {return this.sendJSON(res, { error: `方法不存在: ${methodName}` }, 404);}// 执行函数并获取结果const startTime = Date.now();const result = await module[methodName](params);const executionTime = Date.now() - startTime;// 返回执行结果this.sendJSON(res, {success: true,result: result,executionTime: `${executionTime}ms`,functionName: functionName,methodName: methodName,fromCache: fromCache // 标记是否来自缓存});} catch (error) {console.error('云函数执行错误:', error);this.sendJSON(res, {success: false,error: error.message || '云函数执行失败',stack: error.stack}, 500);}},// 处理云函数重新加载handleReloadFunction(req, res, parsedUrl, bodyData) {try {const { functionName, sourceId } = bodyData;if (!functionName) {return this.sendJSON(res, { error: '缺少必要参数: functionName' }, 400);}// 安全检查 - 防止路径遍历攻击if (functionName.includes('../') || functionName.includes('..\\')) {return this.sendJSON(res, { error: '不允许的函数名' }, 403);}// 检查函数是否存在于任何文件源const functionFilePath = `${functionName}.js`;const existsInSource = async () => {// 1. 检查分布式文件源try {if (sourceId) {// 从指定文件源检查const fileResult = await fileSourceManager.getFileFromSource(sourceId, functionFilePath);return !!fileResult;} else {// 检查任何可用文件源const fileResult = await fileSourceManager.findFile(functionFilePath);return !!fileResult;}} catch (error) {console.error('检查文件源时出错:', error);}// 2. 检查本地文件系统作为后备const localFunctionPath = path.join(this.functionsDir, `${functionName}.js`);return fs.existsSync(localFunctionPath);};existsInSource().then(exists => {if (!exists) {return this.sendJSON(res, { error: `函数文件不存在: ${functionName}.js (未在任何文件源中找到)` }, 404);}// 清除缓存const cleared = this.clearFunctionCache(functionName);this.sendJSON(res, {success: true,message: cleared ? `已重新加载函数: ${functionName}` : `函数不在缓存中,但已准备好重新加载: ${functionName}`,functionName: functionName,sourceId: sourceId || 'all'});}).catch(error => {console.error('检查文件是否存在时出错:', error);return this.sendJSON(res, { error: '检查函数文件时出错' }, 500);});} catch (error) {console.error('重新加载函数错误:', error);this.sendJSON(res, {success: false,error: error.message || '重新加载函数失败'}, 500);}},// 从字符串动态导入模块async importModuleFromString(code, functionName) {// 创建临时URL以导入字符串作为模块const moduleId = `function_${functionName}_${Date.now()}`;// 在Node.js中,我们需要使用动态import和eval的组合// 为了安全考虑,我们需要在沙箱环境中执行代码try {// 将代码转换为ES模块格式const wrappedCode = `// 函数包装器${code}// 导出所有模块导出export default module.exports || {};`;// 使用Module.createRequire创建一个独立的模块上下文const { Module } = await import('module');const require = Module.createRequire(import.meta.url);// 创建一个临时模块对象const tempModule = {exports: {}};// 使用函数构造器执行代码,提供安全的上下文const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;const executeCode = new AsyncFunction('module', 'exports', 'require', '__dirname', '__filename', wrappedCode);// 执行代码await executeCode(tempModule, tempModule.exports, require, this.functionsDir, path.join(this.functionsDir, `${functionName}.js`));// 返回模块导出return tempModule.exports;} catch (error) {console.error(`动态导入模块失败: ${functionName}`, error);throw error;}},// 处理清除所有缓存handleClearCache(req, res) {try {const clearedCount = this.clearAllCache();this.sendJSON(res, {success: true,message: `已清除所有缓存`,clearedCount: clearedCount});} catch (error) {console.error('清除缓存错误:', error);this.sendJSON(res, {success: false,error: error.message || '清除缓存失败'}, 500);}},// 发送JSON响应sendJSON(res, data, statusCode = 200) {res.writeHead(statusCode, {'Content-Type': 'application/json; charset=utf-8'});res.end(JSON.stringify(data, null, 2));}
};// 启动服务器
funServer.init();
funServer.start();

在这里插入图片描述

完美实现,后续还可以接入微服务的逻辑实现分布式的云函数加载,调用

http://www.dtcms.com/a/609673.html

相关文章:

  • TRO侵权预警|Lauren动物插画发起维权
  • Rust实战:使用Axum和SQLx构建高性能RESTful API
  • 波动率曲面分解法在期货价差套利策略中的应用研究
  • 泌阳县住房建设局网站网站seo排名优化工具在线
  • 电子商务网站建设课北京建设网官方网站
  • vr大空间体验馆,vr大空间是什么意思啊?
  • Node.js实现WebSocket教程
  • 朝阳区搜索优化seosem百度seo关键词排名优化工具
  • C++初阶
  • NFS:K8s集群的跨主机存储方案
  • 动态设计网站制作wordpress
  • 短临 Nowcast 在分布式光伏的落地:分钟级降水与云量对 Irradiance 的影响(工程版)
  • linux centos 防火墙操作命令
  • 破解行业绿电直供痛点:直连架构适配关键技术解析
  • token无感刷新全流程
  • MySQL 数据增删改查
  • 浏阳做网站的公司价格网站设计步骤详解
  • 南京做网站外包试论述网上商城的推广技巧
  • 面试150——二叉树
  • opencv 学习: QA_02 什么是图像中的高频成分和低频成分
  • C++_面试题13_QVector和QList的区别
  • Vue 2脚手架从入门到实战核心知识点全解析(day6):从工程结构到高级通信(附代码讲解)
  • 2025年AI面试防作弊指南:技术笔试如何识别异常行为
  • (十)嵌入式面试题收集:15道
  • 标准解读|即将实施的三份汽车安全强制性标准
  • 手机网站建设的流程2024房价即将暴涨十大城市
  • 根系扫描仪实战解析:如何精准获取根长、根表面积与拓扑结构?
  • Mock技术文档
  • 【OpenCV + VS】用addWeighted实现图像线性融合
  • Ubuntu系统创建Conda环境并安装Pytorch(保姆教程)