autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
476 lines (475 loc) • 19.8 kB
JavaScript
/**
* HTTP Server - AutoSnippet 2.0
* 基于 Express 框架的 REST API 服务器
* 集成监控、缓存和错误追踪
*/
import { join } from 'node:path';
import cors from 'cors';
import express from 'express';
import helmet from 'helmet';
import { CapabilityProbe } from '../core/capability/CapabilityProbe.js';
import { registerGatewayActions } from '../core/gateway/GatewayActionRegistry.js';
import { initCacheAdapter } from '../infrastructure/cache/UnifiedCacheAdapter.js';
import Logger from '../infrastructure/logging/Logger.js';
import { initErrorTracker } from '../infrastructure/monitoring/ErrorTracker.js';
import { initPerformanceMonitor } from '../infrastructure/monitoring/PerformanceMonitor.js';
import { initRealtimeService } from '../infrastructure/realtime/RealtimeService.js';
import { getServiceContainer } from '../injection/ServiceContainer.js';
import apiSpec from './api-spec.js';
import { errorHandler } from './middleware/errorHandler.js';
import { gatewayMiddleware } from './middleware/gatewayMiddleware.js';
import { requestLogger } from './middleware/requestLogger.js';
import { roleResolverMiddleware } from './middleware/roleResolver.js';
import aiRouter from './routes/ai.js';
import auditRouter from './routes/audit.js';
import authRouter from './routes/auth.js';
import candidatesRouter from './routes/candidates.js';
import commandsRouter from './routes/commands.js';
import extractRouter from './routes/extract.js';
import guardRouter from './routes/guard.js';
import guardReportRouter from './routes/guardReport.js';
import guardRuleRouter from './routes/guardRules.js';
import healthRouter from './routes/health.js';
import knowledgeRouter from './routes/knowledge.js';
import modulesRouter from './routes/modules.js';
import monitoringRouter from './routes/monitoring.js';
import panoramaRouter from './routes/panorama.js';
import recipesRouter from './routes/recipes.js';
import remoteRouter from './routes/remote.js';
import searchRouter from './routes/search.js';
import signalsRouter from './routes/signals.js';
import skillsRouter from './routes/skills.js';
import taskRouter from './routes/task.js';
import violationsRouter from './routes/violations.js';
import wikiRouter from './routes/wiki.js';
export class HttpServer {
app;
cacheAdapter;
capabilityProbe;
config;
errorTracker;
logger;
performanceMonitor;
realtimeService;
server;
constructor(config = {}) {
this.config = {
port: config.port || 3000,
host: config.host || 'localhost',
enableMonitoring: config.enableMonitoring !== false,
cacheMode: 'memory',
...config,
};
this.logger = Logger.getInstance();
this.app = express();
this.server = null;
this.performanceMonitor = null;
this.errorTracker = null;
this.cacheAdapter = null;
this.realtimeService = null;
this.capabilityProbe = null;
}
/** 初始化服务器 */
async initialize() {
// 初始化监控和缓存服务
await this.initializeServices();
// 注册 Gateway Actions(将 Service 操作绑定到 Gateway 路由)
this.registerGatewayActions();
// 中间件
this.setupMiddleware();
// 路由
this.setupRoutes();
// 错误处理
this.setupErrorHandling();
this.logger.info('HTTP Server initialized', {
port: this.config.port,
host: this.config.host,
cacheMode: this.config.cacheMode,
monitoringEnabled: this.config.enableMonitoring,
timestamp: new Date().toISOString(),
});
}
/** 初始化服务(监控、缓存等) */
async initializeServices() {
try {
// 初始化缓存适配器(纯内存模式)
this.cacheAdapter = await initCacheAdapter({
mode: 'memory',
});
this.logger.info('Cache adapter initialized');
// 初始化性能监控
if (this.config.enableMonitoring) {
this.performanceMonitor = initPerformanceMonitor();
this.logger.info('Performance monitor initialized');
// 初始化错误追踪
this.errorTracker = initErrorTracker();
this.logger.info('Error tracker initialized');
}
}
catch (error) {
this.logger.error('Failed to initialize services', {
error: error.message,
stack: error.stack,
});
throw error;
}
}
/** 设置中间件 */
setupMiddleware() {
// 性能监控中间件(优先级最高)
if (this.performanceMonitor) {
this.app.use(this.performanceMonitor.middleware());
}
// 安全头(放宽 CSP 以兼容 Vite 构建的 Dashboard SPA:script/style 需要内联和 crossorigin)
this.app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'", 'https:'],
imgSrc: ["'self'", 'data:', 'blob:'],
connectSrc: ["'self'", 'ws:', 'wss:'],
fontSrc: ["'self'", 'https:', 'data:'],
objectSrc: ["'none'"],
frameSrc: ["'none'"],
},
},
}));
// 请求日志
this.app.use(requestLogger(this.logger));
// 解析 JSON 请求体
this.app.use(express.json({ limit: '10mb' }));
// 解析 URL 编码的请求体
this.app.use(express.urlencoded({ limit: '10mb', extended: true }));
// 跨域处理 (CORS)
this.app.use(cors({
origin: this.config.corsOrigin || '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: [
'Origin',
'X-Requested-With',
'Content-Type',
'Accept',
'Authorization',
'X-User-Id',
],
credentials: true,
}));
// 角色解析中间件(双路径:token / 探针)
try {
const constitution = getServiceContainer().get('constitution');
const caps = (constitution?.config?.capabilities?.git_write || {});
this.capabilityProbe = new CapabilityProbe({
cacheTTL: caps.cache_ttl || 86400,
noRemote: (caps.no_remote || 'allow'),
});
}
catch {
this.capabilityProbe = new CapabilityProbe();
}
this.app.use(roleResolverMiddleware({ capabilityProbe: this.capabilityProbe }));
// Gateway 中间件 (注入 req.gw)
this.app.use(gatewayMiddleware());
// 请求超时设置(AI 扫描类路由需要更长时间,SSE 流式路由需要更长时间)
this.app.use((req, res, next) => {
const isLongRunning = req.path.includes('/spm/scan') ||
req.path.includes('/spm/bootstrap') ||
req.path.includes('/modules/scan') ||
req.path.includes('/modules/bootstrap') ||
req.path.includes('/extract/');
const isStreaming = req.path.includes('/stream') || req.path.includes('/events/');
req.setTimeout(isLongRunning ? 600000 : isStreaming ? 300000 : 60000); // AI 扫描 10分钟, SSE/EventSource 5分钟, 其他 60秒
next();
});
}
/** 注册 Gateway Actions */
registerGatewayActions() {
try {
const container = getServiceContainer();
const gateway = container.get('gateway');
registerGatewayActions(gateway, container);
this.logger.info('Gateway actions registered');
}
catch (error) {
this.logger.warn('Gateway action registration skipped', {
error: error.message,
});
}
}
/** 设置路由 */
setupRoutes() {
// API 版本前缀
const apiPrefix = '/api/v1';
// OpenAPI 规范
this.app.get('/api-spec', (_req, res) => {
res.json(apiSpec);
});
// 健康检查
this.app.use(`${apiPrefix}/health`, healthRouter);
// 认证路由
this.app.use(`${apiPrefix}/auth`, authRouter);
// 权限探针端点
this.app.get(`${apiPrefix}/auth/probe`, (req, res) => {
const role = req.resolvedRole || 'visitor';
const user = req.resolvedUser || 'anonymous';
const mode = process.env.VITE_AUTH_ENABLED === 'true' || process.env.ASD_AUTH_ENABLED === 'true'
? 'token'
: 'probe';
const probeCache = this.capabilityProbe ? this.capabilityProbe.getCacheStatus() : null;
res.json({
success: true,
data: { role, user, mode, probeCache },
});
});
// 监控端点
if (this.config.enableMonitoring) {
this.app.use(`${apiPrefix}/monitoring`, monitoringRouter);
}
// Guard 实时检查路由(Extension DiagnosticCollection 调用)
this.app.use(`${apiPrefix}/guard`, guardRouter);
// Guard 合规报告路由
this.app.use(`${apiPrefix}/guard/report`, guardReportRouter);
// 守护规则路由
this.app.use(`${apiPrefix}/rules`, guardRuleRouter);
// TaskGraph 路由(Extension taskTool.ts 转发调用)
this.app.use(`${apiPrefix}/task`, taskRouter);
// 搜索路由
this.app.use(`${apiPrefix}/search`, searchRouter);
// AI 路由
this.app.use(`${apiPrefix}/ai`, aiRouter);
// 提取路由
this.app.use(`${apiPrefix}/extract`, extractRouter);
// 命令路由
this.app.use(`${apiPrefix}/commands`, commandsRouter);
// Skills 路由
this.app.use(`${apiPrefix}/skills`, skillsRouter);
// Candidates 路由(AI 补齐/润色)
this.app.use(`${apiPrefix}/candidates`, candidatesRouter);
// Modules 路由(v3.2 统一多语言模块扫描)
this.app.use(`${apiPrefix}/modules`, modulesRouter);
// 违规记录路由
this.app.use(`${apiPrefix}/violations`, violationsRouter);
// 知识条目路由 (V3)
this.app.use(`${apiPrefix}/knowledge`, knowledgeRouter);
// Recipe 操作路由(关系发现等)
this.app.use(`${apiPrefix}/recipes`, recipesRouter);
// Wiki 路由
this.app.use(`${apiPrefix}/wiki`, wikiRouter);
// Remote 路由(飞书 Bot → IDE 远程指令桥接)
this.app.use(`${apiPrefix}/remote`, remoteRouter);
// Panorama 全景路由(项目结构 + 覆盖率 + 健康度)
this.app.use(`${apiPrefix}/panorama`, panoramaRouter);
// 信号留痕 & 报告路由
this.app.use(`${apiPrefix}/signals`, signalsRouter);
// 审计日志路由
this.app.use(`${apiPrefix}/audit`, auditRouter);
// 根路径 — 返回 API 元信息(避免外部探测产生无意义 404)
this.app.all('/', (_req, res) => {
res.json({
name: 'AutoSnippet API',
version: '2.0',
docs: '/api-spec',
health: `${apiPrefix}/health`,
});
});
// 404 处理(使用 app.all 确保 layer.route 存在,mountDashboard 依赖此属性定位并重排路由栈)
this.app.all('{*path}', (req, res) => {
res.status(404).json({
success: false,
error: {
code: 'NOT_FOUND',
message: `Route not found: ${req.method} ${req.originalUrl}`,
},
});
});
}
/** 设置错误处理 */
setupErrorHandling() {
// 使用错误追踪器的错误处理中间件(如果启用)
if (this.errorTracker) {
this.app.use(this.errorTracker.errorHandler());
}
else {
// 全局错误处理中间件(备用)
this.app.use(errorHandler(this.logger));
}
}
/** 启动服务器 */
async start() {
const { promise, resolve, reject } = Promise.withResolvers();
try {
this.server = this.app.listen(this.config.port, this.config.host, () => {
this.logger.info('HTTP Server started', {
host: this.config.host,
port: this.config.port,
url: `http://${this.config.host}:${this.config.port}`,
timestamp: new Date().toISOString(),
});
// 初始化 WebSocket 服务(使用 HTTP 服务器实例)
try {
this.realtimeService = initRealtimeService(this.server);
this.logger.info('Realtime service initialized');
// 桥接 EventBus / SignalBus → RealtimeService
try {
const container = getServiceContainer();
const rs = this.realtimeService;
if (typeof rs?.broadcastEvent !== 'function') {
throw new Error('broadcastEvent not available');
}
// EventBus → lifecycle:transition
const eventBus = container.services.eventBus ? container.get('eventBus') : null;
if (eventBus) {
eventBus.on('lifecycle:transition', (data) => {
rs.broadcastEvent('lifecycle:transition', data);
});
}
// SignalBridge 已将信号转发到 EventBus,HttpServer 只听 EventBus
if (eventBus) {
eventBus.on('signal:event', (signal) => {
rs.broadcastEvent('signal:event', signal);
});
eventBus.on('guard:updated', (signal) => {
rs.broadcastEvent('guard:updated', signal);
});
}
// 确保 SignalBridge 已初始化(触发 lazy singleton)
try {
container.get('signalBridge');
}
catch {
// SignalBridge 未注册时静默跳过
}
// EventBus → audit:entry
if (eventBus) {
eventBus.on('audit:entry', (data) => {
rs.broadcastEvent('audit:entry', data);
});
}
}
catch {
// EventBus/SignalBus 不可用时静默跳过
}
}
catch (error) {
this.logger.warn('Failed to initialize realtime service', {
error: error.message,
});
}
resolve(this.server);
});
this.server.on('error', (error) => {
this.logger.error('HTTP Server error', {
error: error.message,
code: error.code,
timestamp: new Date().toISOString(),
});
reject(error);
});
}
catch (error) {
this.logger.error('Failed to start HTTP Server', {
error: error.message,
timestamp: new Date().toISOString(),
});
reject(error);
}
return promise;
}
/** 停止服务器 */
async stop() {
const { promise, resolve, reject } = Promise.withResolvers();
if (!this.server) {
return resolve(undefined);
}
// 停止性能监控
if (this.performanceMonitor) {
this.performanceMonitor.shutdown();
}
// 停止错误追踪
if (this.errorTracker) {
this.errorTracker.shutdown();
}
// 关闭 WebSocket 连接
if (this.realtimeService && typeof this.realtimeService.shutdown === 'function') {
try {
this.realtimeService.shutdown();
}
catch (err) {
this.logger.warn('Error shutting down realtime service', {
error: err.message,
});
}
}
this.server.close((error) => {
if (error) {
this.logger.error('Error stopping HTTP Server', {
error: error.message,
timestamp: new Date().toISOString(),
});
return reject(error);
}
this.logger.info('HTTP Server stopped', {
timestamp: new Date().toISOString(),
});
resolve(undefined);
});
return promise;
}
/** 获取 Express 应用实例 */
getApp() {
return this.app;
}
/**
* 挂载 Dashboard 静态资源(生产模式:直接托管预构建产物)
* 必须在 initialize() + start() 之后调用
* @param distDir dashboard/dist 目录的绝对路径
*/
mountDashboard(distDir) {
// 从路由栈中移除最后的 404 catch-all 和根路径 handler
// Express 5 使用 app.router(Express 4 为 app._router)
const router = this.app.router ?? this.app._router;
if (!router) {
this.logger.warn('mountDashboard: Express router not available, mounting without route reordering');
this.app.use(express.static(distDir));
this.app.get('{*path}', (req, res, next) => {
if (req.path.startsWith('/api') || req.path.startsWith('/socket.io')) {
return next();
}
res.sendFile(join(distDir, 'index.html'));
});
this.logger.info('Dashboard mounted (production mode, fallback)', { distDir });
return;
}
const layers = router.stack;
// 倒序弹出最后 2 层(404 + root handler)
const removedLayers = [];
for (let i = layers.length - 1; i >= 0; i--) {
const layer = layers[i];
if (layer.route) {
removedLayers.unshift(layers.splice(i, 1)[0]);
if (removedLayers.length >= 2) {
break;
}
}
}
// 注入 express.static 托管 dist 目录
this.app.use(express.static(distDir));
// SPA fallback: 非 API / 非 socket.io 请求返回 index.html
this.app.get('{*path}', (req, res, next) => {
if (req.path.startsWith('/api') || req.path.startsWith('/socket.io')) {
return next();
}
res.sendFile(join(distDir, 'index.html'));
});
// 放回 404 handler(SPA fallback 之后,作为兜底)
for (const layer of removedLayers) {
layers.push(layer);
}
this.logger.info('Dashboard mounted (production mode)', { distDir });
}
/** 获取服务器实例 */
getServer() {
return this.server;
}
}
export default HttpServer;