import { Request, Response, NextFunction } from 'express'; import { MissingMaterialItem } from './models'; export class AppError extends Error { public statusCode: number; public errorCode: number; constructor(message: string, statusCode: number, errorCode: number) { super(message); this.statusCode = statusCode; this.errorCode = errorCode; Object.setPrototypeOf(this, AppError.prototype); } } export class MissingMaterialError extends AppError { public missingItems: MissingMaterialItem[]; constructor(missingItems: MissingMaterialItem[]) { super('need_more_material', 400, 4001); this.missingItems = missingItems; Object.setPrototypeOf(this, MissingMaterialError.prototype); } } // 请求日志中间件 export const requestLogger = (req: Request, res: Response, next: NextFunction) => { const started_at = new Date(); // 拦截 res.send 记录结束时间 const originalSend = res.send; res.send = function (body: any) { const ended_at = new Date(); // 提取 case_context const case_context = req.body?.case_context || req.body; const case_id = case_context?.case_id || 'unknown'; // 提取 meta 数据和 retry_count const metadata = case_context?.metadata || {}; const retry_count = metadata.retry_count || req.headers['x-retry-count'] || req.body?.retry_count || 0; // 解析 agent_name let agent_name = 'unknown'; if (req.path.includes('peer-reg-summary')) agent_name = 'AGENT-01'; else if (req.path.includes('policy-rewrite')) agent_name = 'AGENT-02'; else if (req.path.includes('compliance-check')) agent_name = 'AGENT-03'; else if (req.path.includes('legal-pack')) agent_name = 'AGENT-04'; else if (req.path.includes('log-analysis')) agent_name = 'LOG_ANALYSIS'; else if (req.path.includes('code-security')) agent_name = 'CODE_SECURITY'; else if (req.path.includes('regulatory-penalty-summary')) agent_name = 'REG_PENALTY_SUMMARY'; const status = res.statusCode >= 400 ? 'failed' : 'success'; const error_code = res.locals.errorCode || 0; const api_latency_ms = ended_at.getTime() - started_at.getTime(); const llm_latency_ms = res.locals.llm_latency_ms || 0; // 确保绝对不记录 materials,只输出 meta 数据 console.log(JSON.stringify({ case_id, retry_count, agent_name, metadata, started_at: started_at.toISOString(), ended_at: ended_at.toISOString(), api_latency_ms, llm_latency_ms, status, error_code })); return originalSend.call(this, body); }; next(); }; // 全局错误处理中间件 export const errorHandler = (err: any, req: Request, res: Response, next: NextFunction) => { let statusCode = 500; let errorCode = 5000; let message = 'Internal Server Error'; let extraData: any = null; if (err instanceof MissingMaterialError) { statusCode = err.statusCode; errorCode = err.errorCode; message = err.message; extraData = { missing_materials: err.missingItems }; } else if (err instanceof AppError) { statusCode = err.statusCode; errorCode = err.errorCode; message = err.message; } else if (err.message === 'LLM_INFERENCE_FAILED') { statusCode = 500; errorCode = 5001; message = '智能体推理失败'; } else if (err.message === 'MISSING_MATERIALS') { statusCode = 400; errorCode = 4001; message = 'need_more_material'; } else if (err.message === 'INVALID_FORMAT') { statusCode = 400; errorCode = 4002; message = '输入格式错误'; } else if (err.message === 'MISSING_CONTEXT') { statusCode = 500; errorCode = 5002; message = '外部上下文缺失'; } else if (err.message === 'MISSING_LLM_API_KEY') { statusCode = 500; errorCode = 5003; message = 'LLM密钥未配置'; } // 记录 errorCode 给 logger 使用 res.locals.errorCode = errorCode; if (err instanceof MissingMaterialError) { res.status(statusCode).json({ schema_version: 'v1.0', error_code: 'need_more_material', message: message, ...(extraData || {}) }); } else { res.status(statusCode).json({ error: { code: errorCode, message: message, ...(extraData || {}) } }); } };