完成v1.1.0
This commit is contained in:
88
website/src/controllers/apiController.js
Normal file
88
website/src/controllers/apiController.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* API控制器
|
||||
* 提供系统状态和健康检查接口
|
||||
*/
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
*/
|
||||
exports.healthCheck = (req, res) => {
|
||||
const health = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
memory: process.memoryUsage(),
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
pid: process.pid
|
||||
};
|
||||
|
||||
res.json(health);
|
||||
};
|
||||
|
||||
/**
|
||||
* 性能指标
|
||||
*/
|
||||
exports.performanceMetrics = (req, res) => {
|
||||
const metrics = {
|
||||
timestamp: new Date().toISOString(),
|
||||
memory: {
|
||||
heapUsed: process.memoryUsage().heapUsed,
|
||||
heapTotal: process.memoryUsage().heapTotal,
|
||||
rss: process.memoryUsage().rss
|
||||
},
|
||||
cpu: process.cpuUsage(),
|
||||
uptime: process.uptime(),
|
||||
activeHandles: process._getActiveHandles().length,
|
||||
activeRequests: process._getActiveRequests().length
|
||||
};
|
||||
|
||||
res.json(metrics);
|
||||
};
|
||||
|
||||
/**
|
||||
* 服务器信息
|
||||
*/
|
||||
exports.serverInfo = (req, res) => {
|
||||
const info = {
|
||||
name: 'FutureOSS 官方网站',
|
||||
version: '1.0.0',
|
||||
description: 'FutureOSS 插件化运行时框架官方网站',
|
||||
repository: 'https://gitee.com/starlight-apk/future-oss',
|
||||
endpoints: [
|
||||
{ path: '/', method: 'GET', description: '首页' },
|
||||
{ path: '/features', method: 'GET', description: '特性页面' },
|
||||
{ path: '/architecture', method: 'GET', description: '架构页面' },
|
||||
{ path: '/plugins', method: 'GET', description: '插件页面' },
|
||||
{ path: '/api/health', method: 'GET', description: '健康检查' },
|
||||
{ path: '/api/metrics', method: 'GET', description: '性能指标' },
|
||||
{ path: '/api/info', method: 'GET', description: '服务器信息' }
|
||||
],
|
||||
environment: process.env.NODE_ENV || 'development'
|
||||
};
|
||||
|
||||
res.json(info);
|
||||
};
|
||||
|
||||
/**
|
||||
* 压力测试端点(仅开发环境)
|
||||
*/
|
||||
exports.stressTest = (req, res) => {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
return res.status(403).json({ error: '仅开发环境可用' });
|
||||
}
|
||||
|
||||
const n = parseInt(req.query.n) || 1000000;
|
||||
let result = 0;
|
||||
|
||||
// 模拟CPU密集型操作
|
||||
for (let i = 0; i < n; i++) {
|
||||
result += Math.sqrt(i) * Math.sin(i);
|
||||
}
|
||||
|
||||
res.json({
|
||||
result: result,
|
||||
iterations: n,
|
||||
message: '压力测试完成'
|
||||
});
|
||||
};
|
||||
48
website/src/controllers/pagesController.js
Normal file
48
website/src/controllers/pagesController.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 页面控制器
|
||||
* 每个方法只负责渲染一个页面,业务逻辑分离
|
||||
*/
|
||||
|
||||
/**
|
||||
* 渲染首页
|
||||
*/
|
||||
exports.renderHome = (req, res) => {
|
||||
res.render('pages/home', {
|
||||
title: 'FutureOSS - 一切皆为插件的开发者运行时框架',
|
||||
page: 'home',
|
||||
description: 'FutureOSS 是一款面向开发者的插件化运行时框架,秉承「一切皆为插件」的设计理念,让功能扩展变得前所未有的简单。'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染特性页面
|
||||
*/
|
||||
exports.renderFeatures = (req, res) => {
|
||||
res.render('pages/features', {
|
||||
title: '核心特性 - FutureOSS',
|
||||
page: 'features',
|
||||
description: 'FutureOSS 的核心特性:插件化架构、安全沙箱、热重载支持、可视化控制台、双协议服务、依赖自动解析。'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染架构页面
|
||||
*/
|
||||
exports.renderArchitecture = (req, res) => {
|
||||
res.render('pages/architecture', {
|
||||
title: '技术架构 - FutureOSS',
|
||||
page: 'architecture',
|
||||
description: '深入了解 FutureOSS 的技术架构和设计思想,包括插件系统架构、安全机制、数据流等。'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染插件页面
|
||||
*/
|
||||
exports.renderPlugins = (req, res) => {
|
||||
res.render('pages/plugins', {
|
||||
title: '插件生态系统 - FutureOSS',
|
||||
page: 'plugins',
|
||||
description: 'FutureOSS 插件生态系统:核心插件、社区插件、插件开发指南、插件市场。'
|
||||
});
|
||||
};
|
||||
218
website/src/middleware/performance.js
Normal file
218
website/src/middleware/performance.js
Normal file
@@ -0,0 +1,218 @@
|
||||
/**
|
||||
* 性能优化中间件
|
||||
*/
|
||||
|
||||
/**
|
||||
* 响应时间头中间件
|
||||
* 添加X-Response-Time头到响应中
|
||||
*/
|
||||
function responseTime(req, res, next) {
|
||||
const start = Date.now();
|
||||
|
||||
// 保存原始的 end 方法
|
||||
const originalEnd = res.end;
|
||||
|
||||
// 重写 end 方法以在响应发送前设置头部
|
||||
res.end = function(...args) {
|
||||
const duration = Date.now() - start;
|
||||
|
||||
// 只有在头部尚未发送时才能设置
|
||||
if (!res.headersSent) {
|
||||
res.setHeader('X-Response-Time', `${duration}ms`);
|
||||
}
|
||||
|
||||
// 记录慢响应
|
||||
if (duration > 1000) {
|
||||
console.warn(`慢响应: ${req.method} ${req.url} - ${duration}ms`);
|
||||
}
|
||||
|
||||
// 调用原始的 end 方法
|
||||
return originalEnd.apply(this, args);
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩中间件
|
||||
* 已经使用compression,这里添加额外的压缩头
|
||||
*/
|
||||
function compressionHeaders(req, res, next) {
|
||||
// 为文本资源启用压缩
|
||||
if (req.url.match(/\.(html|css|js|json|xml)$/)) {
|
||||
res.setHeader('Vary', 'Accept-Encoding');
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存控制中间件
|
||||
*/
|
||||
function cacheControl(req, res, next) {
|
||||
// 静态资源缓存策略
|
||||
if (req.url.match(/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/)) {
|
||||
// 静态资源缓存1年
|
||||
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
|
||||
} else if (req.url.match(/\.(html)$/)) {
|
||||
// HTML文件不缓存
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
} else {
|
||||
// 默认缓存策略
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全头中间件
|
||||
*/
|
||||
function securityHeaders(req, res, next) {
|
||||
// 防止MIME类型嗅探
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
// 防止点击劫持
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
|
||||
// XSS保护
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
|
||||
// 推荐使用HTTPS
|
||||
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||
|
||||
// 内容安全策略
|
||||
const csp = [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
|
||||
"style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com",
|
||||
"font-src 'self' https://cdnjs.cloudflare.com https://fonts.gstatic.com",
|
||||
"img-src 'self' data: https:",
|
||||
"connect-src 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"base-uri 'self'",
|
||||
"form-action 'self'"
|
||||
].join('; ');
|
||||
|
||||
res.setHeader('Content-Security-Policy', csp);
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gzip预压缩中间件
|
||||
* 检查是否存在预压缩的.gz文件
|
||||
*/
|
||||
function gzipStatic(req, res, next) {
|
||||
const acceptEncoding = req.headers['accept-encoding'] || '';
|
||||
|
||||
if (acceptEncoding.includes('gzip') && req.url.match(/\.(js|css|html|json)$/)) {
|
||||
req.url = req.url + '.gz';
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
|
||||
// 设置正确的Content-Type
|
||||
if (req.url.endsWith('.js.gz')) {
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
} else if (req.url.endsWith('.css.gz')) {
|
||||
res.setHeader('Content-Type', 'text/css');
|
||||
} else if (req.url.endsWith('.html.gz')) {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求限流中间件(简单版本)
|
||||
*/
|
||||
function rateLimiter(maxRequests = 100, windowMs = 15 * 60 * 1000) {
|
||||
const requests = new Map();
|
||||
|
||||
return function(req, res, next) {
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
const now = Date.now();
|
||||
|
||||
if (!requests.has(ip)) {
|
||||
requests.set(ip, []);
|
||||
}
|
||||
|
||||
const timestamps = requests.get(ip);
|
||||
|
||||
// 清理过期的请求记录
|
||||
const windowStart = now - windowMs;
|
||||
while (timestamps.length && timestamps[0] < windowStart) {
|
||||
timestamps.shift();
|
||||
}
|
||||
|
||||
// 检查是否超过限制
|
||||
if (timestamps.length >= maxRequests) {
|
||||
res.status(429).json({
|
||||
error: '请求过多',
|
||||
message: '请稍后再试'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录当前请求
|
||||
timestamps.push(now);
|
||||
|
||||
// 设置限流头
|
||||
res.setHeader('X-RateLimit-Limit', maxRequests);
|
||||
res.setHeader('X-RateLimit-Remaining', maxRequests - timestamps.length);
|
||||
res.setHeader('X-RateLimit-Reset', Math.ceil((timestamps[0] + windowMs) / 1000));
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据库查询优化中间件(示例)
|
||||
*/
|
||||
function queryOptimizer(req, res, next) {
|
||||
// 这里可以添加数据库查询优化逻辑
|
||||
// 例如:限制查询结果数量、添加索引提示等
|
||||
|
||||
// 示例:为API请求添加默认分页
|
||||
if (req.path.startsWith('/api/') && req.method === 'GET') {
|
||||
req.query.limit = req.query.limit || '50';
|
||||
req.query.offset = req.query.offset || '0';
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存使用监控
|
||||
*/
|
||||
function memoryMonitor(req, res, next) {
|
||||
const memoryUsage = process.memoryUsage();
|
||||
|
||||
// 记录高内存使用
|
||||
if (memoryUsage.heapUsed > 500 * 1024 * 1024) { // 500MB
|
||||
console.warn('高内存使用:', {
|
||||
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB',
|
||||
heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB',
|
||||
rss: Math.round(memoryUsage.rss / 1024 / 1024) + 'MB',
|
||||
url: req.url
|
||||
});
|
||||
}
|
||||
|
||||
// 添加内存使用头(仅开发环境)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
res.setHeader('X-Memory-Usage', Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB');
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
responseTime,
|
||||
compressionHeaders,
|
||||
cacheControl,
|
||||
securityHeaders,
|
||||
gzipStatic,
|
||||
rateLimiter,
|
||||
queryOptimizer,
|
||||
memoryMonitor
|
||||
};
|
||||
40
website/src/router.js
Normal file
40
website/src/router.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* FutureOSS 网站路由配置
|
||||
* 一个文件只管一个功能:路由只负责路由,不处理业务逻辑
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const path = require('path');
|
||||
|
||||
// 导入页面控制器
|
||||
const pagesController = require('./controllers/pagesController');
|
||||
const apiController = require('./controllers/apiController');
|
||||
|
||||
// API路由
|
||||
router.get('/api/health', apiController.healthCheck);
|
||||
router.get('/api/metrics', apiController.performanceMetrics);
|
||||
router.get('/api/info', apiController.serverInfo);
|
||||
router.get('/api/stress-test', apiController.stressTest);
|
||||
|
||||
// 页面路由 - 每个路由对应一个独立的页面文件
|
||||
router.get('/', pagesController.renderHome);
|
||||
router.get('/features', pagesController.renderFeatures);
|
||||
router.get('/architecture', pagesController.renderArchitecture);
|
||||
router.get('/plugins', pagesController.renderPlugins);
|
||||
|
||||
// 静态文件路由(备用)
|
||||
router.get('/static/*', (req, res) => {
|
||||
const filePath = path.join(__dirname, '../public', req.params[0]);
|
||||
res.sendFile(filePath, (err) => {
|
||||
if (err) {
|
||||
res.status(404).json({ error: '文件未找到' });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 重定向旧路由(如果需要)
|
||||
router.get('/home', (req, res) => res.redirect('/'));
|
||||
router.get('/index', (req, res) => res.redirect('/'));
|
||||
|
||||
module.exports = router;
|
||||
175
website/src/server.js
Normal file
175
website/src/server.js
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* FutureOSS 官方网站服务器
|
||||
* 支持端口自动切换:8080被占用则使用8081
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const morgan = require('morgan');
|
||||
const cors = require('cors');
|
||||
const compression = require('compression');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const expressLayouts = require('express-ejs-layouts');
|
||||
const performanceMiddleware = require('./middleware/performance');
|
||||
|
||||
// 创建Express应用
|
||||
const app = express();
|
||||
|
||||
// 中间件配置
|
||||
app.use(morgan('dev')); // 日志
|
||||
app.use(cors()); // CORS支持
|
||||
app.use(compression()); // 压缩响应
|
||||
app.use(performanceMiddleware.responseTime); // 响应时间监控
|
||||
app.use(performanceMiddleware.compressionHeaders); // 压缩头
|
||||
app.use(performanceMiddleware.cacheControl); // 缓存控制
|
||||
app.use(performanceMiddleware.securityHeaders); // 安全头
|
||||
app.use(performanceMiddleware.memoryMonitor); // 内存监控
|
||||
app.use(express.json()); // JSON解析
|
||||
app.use(express.urlencoded({ extended: true })); // URL编码解析
|
||||
|
||||
// 静态文件服务
|
||||
app.use(express.static(path.join(__dirname, '../public')));
|
||||
|
||||
// 设置视图引擎和布局
|
||||
app.use(expressLayouts);
|
||||
app.set('layout', 'layouts/main');
|
||||
app.set('view engine', 'ejs');
|
||||
app.set('views', path.join(__dirname, '../views'));
|
||||
|
||||
// 导入路由
|
||||
const router = require('./router');
|
||||
app.use('/', router);
|
||||
|
||||
// 错误处理中间件
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('服务器错误:', err);
|
||||
res.status(500).json({
|
||||
error: '服务器内部错误',
|
||||
message: process.env.NODE_ENV === 'development' ? err.message : undefined
|
||||
});
|
||||
});
|
||||
|
||||
// 404处理
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({ error: '页面未找到' });
|
||||
});
|
||||
|
||||
/**
|
||||
* 检查端口是否可用
|
||||
* @param {number} port - 要检查的端口
|
||||
* @returns {Promise<boolean>} - 端口是否可用
|
||||
*/
|
||||
function checkPort(port) {
|
||||
return new Promise((resolve) => {
|
||||
const net = require('net');
|
||||
const server = net.createServer();
|
||||
|
||||
server.once('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.log(`端口 ${port} 已被占用`);
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
server.once('listening', () => {
|
||||
server.close();
|
||||
console.log(`端口 ${port} 可用`);
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
server.listen(port);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用端口
|
||||
* @param {number} startPort - 起始端口
|
||||
* @param {number} maxAttempts - 最大尝试次数
|
||||
* @returns {Promise<number>} - 可用的端口
|
||||
*/
|
||||
async function getAvailablePort(startPort = 8080, maxAttempts = 10) {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const port = startPort + i;
|
||||
const isAvailable = await checkPort(port);
|
||||
if (isAvailable) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
throw new Error(`在端口 ${startPort} 到 ${startPort + maxAttempts - 1} 范围内找不到可用端口`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动服务器
|
||||
*/
|
||||
async function startServer() {
|
||||
try {
|
||||
// 获取可用端口
|
||||
const port = await getAvailablePort(8080, 5);
|
||||
|
||||
// 启动服务器
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`
|
||||
╔══════════════════════════════════════════════════════════╗
|
||||
║ FutureOSS 官方网站 ║
|
||||
╠══════════════════════════════════════════════════════════╣
|
||||
║ 服务器已启动! ║
|
||||
║ ║
|
||||
║ 本地访问: http://localhost:${port} ║
|
||||
║ 环境: ${process.env.NODE_ENV || 'development'} ║
|
||||
║ 进程ID: ${process.pid} ║
|
||||
║ ║
|
||||
║ 可用路由: ║
|
||||
║ • GET / - 首页 ║
|
||||
║ • GET /features - 特性页面 ║
|
||||
║ • GET /architecture - 架构页面 ║
|
||||
║ • GET /plugins - 插件页面 ║
|
||||
║ • GET /api/health - 健康检查 ║
|
||||
║ ║
|
||||
║ 按 Ctrl+C 停止服务器 ║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
`);
|
||||
});
|
||||
|
||||
// 优雅关闭
|
||||
const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
|
||||
signals.forEach(signal => {
|
||||
process.on(signal, () => {
|
||||
console.log(`\n接收到 ${signal} 信号,正在关闭服务器...`);
|
||||
server.close(() => {
|
||||
console.log('服务器已关闭');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
// 5秒后强制退出
|
||||
setTimeout(() => {
|
||||
console.error('强制关闭服务器');
|
||||
process.exit(1);
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
|
||||
// 未捕获异常处理
|
||||
process.on('uncaughtException', (err) => {
|
||||
console.error('未捕获异常:', err);
|
||||
server.close(() => process.exit(1));
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('未处理的Promise拒绝:', reason);
|
||||
});
|
||||
|
||||
return server;
|
||||
} catch (error) {
|
||||
console.error('启动服务器失败:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是直接运行此文件,则启动服务器
|
||||
if (require.main === module) {
|
||||
startServer();
|
||||
}
|
||||
|
||||
module.exports = { app, startServer };
|
||||
Reference in New Issue
Block a user