feat: 初始化项目结构并添加核心功能模块

- 新增文档模板和导航结构
- 实现服务器基础API路由和控制器
- 添加扩展插件配置和前端框架
- 引入多租户和权限管理模块
- 集成日志和数据库配置
- 添加核心业务模型和类型定义
This commit is contained in:
2026-03-17 22:07:19 +08:00
parent c0870dce50
commit 136c2fa579
728 changed files with 107690 additions and 5614 deletions

View File

@@ -0,0 +1,36 @@
import { Request, Response, NextFunction } from 'express';
import { logger } from '../../utils/logger';
/**
* [CORE_SEC_07] 全链路 mTLS 强制加密守卫
* @description 校验请求是否携带合法的客户端证书。在生产环境下,由 Nginx/ALB 负责终止 mTLS 并通过 Header 传递证书信息。
*/
export const mtlsGuard = (req: Request, res: Response, next: NextFunction) => {
// 1. 获取证书指纹或序列号 (假设由反向代理通过 Header 传递)
const certFingerprint = req.header('X-Client-Cert-Fingerprint');
const certSubject = req.header('X-Client-Cert-Subject');
// 2. 如果是开发环境且未配置 mTLS则跳过 (可选)
if (process.env.NODE_ENV === 'development' && !process.env.STRICT_MTLS) {
return next();
}
// 3. 校验指纹是否存在
if (!certFingerprint) {
logger.error(`[mTLS] Request blocked: Missing client certificate fingerprint. IP: ${req.ip}`);
return res.status(403).json({
success: false,
error: 'mTLS_REQUIRED',
message: 'Secure connection via client certificate is required.'
});
}
// 4. 将证书信息挂载到请求上下文,供后续业务逻辑使用 (如 NodeIdentityService)
(req as any).mtlsContext = {
fingerprint: certFingerprint,
subject: certSubject
};
logger.info(`[mTLS] Validated client certificate: ${certSubject} (${certFingerprint.substring(0, 8)}...)`);
next();
};

View File

@@ -0,0 +1,34 @@
import { Request, Response, NextFunction } from 'express';
import { RBACEngine } from '../auth/RBACEngine';
import { logger } from '../../utils/logger';
/**
* [BIZ_RBAC_01] RBAC 权限校验门禁
* @param requiredPermission 所需权限点 (如 'product:write')
*/
export const requirePermission = (requiredPermission: string) => {
return async (req: Request, res: Response, next: NextFunction) => {
const context = (req as any).traceContext;
if (!context || !context.userId || !context.tenantId) {
logger.error(`[RBAC] Missing auth context for request ${req.path}`);
return res.status(401).json({
success: false,
error: 'Unauthorized: Missing security context'
});
}
// [V32.0] 使用 RBACEngine.authorize 进行异步权限校验
const hasAccess = await RBACEngine.authorize(context.userId, context.tenantId, requiredPermission);
if (!hasAccess) {
logger.warn(`[RBAC] User ${context.userId} denied permission [${requiredPermission}] for tenant ${context.tenantId}`);
return res.status(403).json({
success: false,
error: `Forbidden: Missing required permission [${requiredPermission}]`
});
}
next();
};
};

View File

@@ -0,0 +1,43 @@
import { NextFunction, Request, Response } from 'express';
import db from '../../config/database';
import { SLAGovernanceService } from '../../domains/Billing/SLAGovernanceService';
import { logger } from '../../utils/logger';
/**
* [CORE_TOB_06] SLA 熔断中间件
* @description 在请求进入前检查租户状态,请求结束后记录指标
*/
export async function slaGuard(req: Request, res: Response, next: NextFunction) {
// 公开路由白名单
const PUBLIC_ROUTES = ['/api/v1/auth/login', '/health'];
if (PUBLIC_ROUTES.includes(req.path)) {
return next();
}
const context = (req as any).traceContext;
if (!context || !context.tenantId) return next();
const tenantId = context.tenantId;
// 1. 检查租户是否被熔断 (SUSPENDED)
const tenant = await db('cf_tenants').where({ id: tenantId }).first();
if (tenant && tenant.status === 'SUSPENDED') {
logger.warn(`[SLA] Request blocked for suspended tenant: ${tenantId}`);
return res.status(429).json({
success: false,
error: 'Tenant SLA breach: Service temporarily suspended. Please contact support.'
});
}
// 2. 拦截响应以记录指标
const originalJson = res.json;
res.json = function(data: any) {
const success = res.statusCode >= 200 && res.statusCode < 300;
SLAGovernanceService.recordMetric(tenantId, success).catch(err =>
logger.error(`[SLA] Failed to record metric: ${err.message}`)
);
return originalJson.call(this, data);
};
next();
}

View File

@@ -0,0 +1,36 @@
const PRODUCT_STATUS_TRANSITIONS: Record<string, string[]> = {
DRAFTED: ['PENDING_REVIEW'],
PENDING_REVIEW: ['APPROVED', 'FAILED_MANUAL'],
APPROVED: ['EXECUTING'],
EXECUTING: ['PUBLISHED', 'FAILED_RETRYABLE', 'FAILED_MANUAL'],
PUBLISHED: ['RECONCILED'],
RECONCILED: [],
FAILED_RETRYABLE: ['EXECUTING', 'FAILED_MANUAL'],
FAILED_MANUAL: ['PENDING_REVIEW'],
};
const SYNC_STATUS_TRANSITIONS: Record<string, string[]> = {
pending: ['published', 'failed'],
failed: ['pending'],
published: ['published'],
};
export function isValidProductStatusTransition(fromStatus: string, toStatus: string) {
const normalizedFrom = fromStatus.toUpperCase();
const normalizedTo = toStatus.toUpperCase();
const allowed = PRODUCT_STATUS_TRANSITIONS[normalizedFrom];
if (!allowed) {
return false;
}
return allowed.includes(normalizedTo);
}
export function isValidSyncStatusTransition(fromStatus: string, toStatus: string) {
const normalizedFrom = fromStatus.toLowerCase();
const normalizedTo = toStatus.toLowerCase();
const allowed = SYNC_STATUS_TRANSITIONS[normalizedFrom];
if (!allowed) {
return false;
}
return allowed.includes(normalizedTo);
}

View File

@@ -0,0 +1,85 @@
import { NextFunction, Request, Response } from 'express';
import { AuthService } from '../../services/AuthService';
const REQUIRED_HEADERS = [
'x-tenant-id',
'x-trace-id',
] as const;
/**
* [CORE_GOV_05] 运行时强门禁: 租户上下文与身份验证
* @description 优先从 JWT 提取上下文,否则从 Header 提取 (仅限系统/节点调用)
*/
export function requireTraceContext(req: Request, res: Response, next: NextFunction) {
// 公开路由白名单
const PUBLIC_ROUTES = ['/api/v1/auth/login', '/health'];
if (PUBLIC_ROUTES.includes(req.path)) {
return next();
}
let context: any = null;
// 1. 尝试从 Authorization: Bearer <Token> 提取 JWT
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7);
const payload = AuthService.verifyToken(token);
if (payload) {
context = {
tenantId: payload.tenantId,
shopId: payload.shopId,
userId: payload.userId,
roleCode: payload.role,
taskId: String(req.headers['x-task-id'] || ''),
traceId: String(req.headers['x-trace-id'] || `trace-${Date.now()}`),
source: 'jwt'
};
} else {
return res.status(401).json({ success: false, error: 'Invalid or expired authentication token' });
}
}
// 2. 如果没有 JWT检查 Header 门禁 (适用于 Node Agent / Extension 快速调用)
if (!context) {
const missing = REQUIRED_HEADERS.filter((header) => {
const value = req.headers[header];
return typeof value !== 'string' || value.trim().length === 0;
});
if (missing.length > 0) {
return res.status(400).json({
success: false,
error: `Missing required trace context headers: ${missing.join(', ')}`,
});
}
context = {
tenantId: String(req.headers['x-tenant-id']),
shopId: String(req.headers['x-shop-id'] || ''),
taskId: String(req.headers['x-task-id'] || ''),
traceId: String(req.headers['x-trace-id']),
userId: String(req.headers['x-user-id'] || 'system'),
roleCode: String(req.headers['x-role-code'] || 'UNKNOWN'),
source: 'header'
};
}
// 3. 挂载上下文
(req as any).traceContext = context;
next();
}
/**
* [CORE_AUTH_01] 仅限管理员或特定角色访问的门禁
*/
export function authorize(roles: string[]) {
return (req: Request, res: Response, next: NextFunction) => {
const context = (req as any).traceContext;
if (!context || !roles.includes(context.roleCode)) {
return res.status(403).json({ success: false, error: 'Forbidden: Insufficient permissions' });
}
next();
};
}