一个设备管理系统从传统 CRUD 到 AI-Native 需要迈过几道坎?本文拆解 EAMX 2.0 的完整技术架构:11 个插件的模块化设计、14 个 AI Agent 的协作管线、CASL 同构权限体系、以及一次架构评审触发的三阶段重构。
1. 项目定位
EAMX 是一个 AI-Native 企业设备资产管理系统(CMMS),对标 MaintainX。核心差异化在于:用户通过自然语言对话完成设备建档、故障报修、备件查询等操作,而不是在几十个菜单和表单中手动操作。
技术定位:多租户 SaaS + 插件化架构 + AI 管道 + CASL 权限 + MCP 标准工具暴露。
💡 为什么选择 AI-Native 而不是"AI加持"?
传统 EAM 产品是先有流程、再叠加 AI 功能。EAMX 从第一天起就把 AI Agent 作为主交互入口,所有业务逻辑通过 Tool Calling 触达,AI 不是装饰,是操作系统。
2. 全栈技术栈一览
┌─ 前端 ─────────────────────────────────────┐
│ React 19 + TypeScript │
│ Vite 7 (构建) │
│ wouter (路由) + React Query (状态) │
│ shadcn/ui (55 个组件) │
│ CASL (前端权限消费) │
├─────────────────────────────────────────────┤
│ packages/core (前后端共享) │
│ PluginRegistry / Ability Builder / Types │
├─ 后端 ─────────────────────────────────────┐
│ Express 5 + TypeScript │
│ Drizzle ORM (类型安全 SQL) │
│ PostgreSQL 16 (主库 + pgvector + Session) │
│ Vercel AI SDK (Tool Calling + SSE) │
│ OpenAI 兼容 API (SiliconFlow 代理) │
│ pino-http (日志) │
└─────────────────────────────────────────────┘
每一个技术选型背后都有明确的理由,下表列出关键决策:
| 决策 | 选型 | 理由 |
|---|---|---|
| 前后端共享类型 | packages/core pnpm workspace | 权限码、插件接口、Tool 类型共享 |
| ORM | Drizzle ORM | 类型安全的 SQL builder,无运行时开销 |
| AI SDK | Vercel AI SDK streamText | 原生 Tool Calling + SSE 流式 |
| 权限 | CASL createMongoAbility | 前后端同构,条件权限支持 |
| 模型代理 | OpenAI 兼容 API → SiliconFlow | 一行 baseURL 切换任意模型厂商 |
3. 多租户架构
3.1 租户隔离策略
所有业务表以 tenant_id 为第一隔离维度:
-- 所有查询自动注入租户条件
SELECT * FROM equipments WHERE tenant_id = $1;
// Drizzle ORM 层:租户过滤器工具函数
export function wt<T>(tenantId: string | null, ...conditions: SQL[]) {
return and(eq(table.tenantId, tenantId), ...conditions);
}
选择应用层隔离而非 PostgreSQL Row-Level Security,理由有三:
- Drizzle ORM 层面统一处理,不会因遗漏 RLS 策略导致数据泄露
- 查询计划可预测,不依赖 PostgreSQL 的 RLS 优化器行为
- 开发时更直观——"看到
tenant_id条件就是安全的"
3.2 跨租户管理
平台超级管理员(isAdmin=true, tenantId=null)可以跨越租户边界:
// auth.ts — writeAdminSession
s.isAdmin = true;
s.tenantId = null; // null = 跨租户
s.dataScope = "all";
在 Drizzle 查询层,tenantId=null 时跳过租户过滤,从而看到全局数据。这个设计让平台运营人员能够在单一界面管理所有租户,而不需要切换账号。
4. 插件化架构:11 个独立模块
4.1 插件清单
| 插件 ID | 路由前缀 | 职责 |
|---|---|---|
| core-data | /departments, /users, /roles, /admin, /workflows... | 组织架构、用户、审批流核心 |
| equipment | /equipments, /equipment-categories, /locations | 设备台账 |
| technical-standards | /technical-standards | 技术标准 |
| work-orders | /work-orders | 工单管理 |
| pm-plans | /pm-plans | 预防性维护 |
| fault-reports | /fault-reports | 故障上报 |
| inventory | /inventory, /spare-parts, /purchase-requests | 库存备件 |
| notifications | /notifications | 通知中心 |
| ai-chat | /ai-chat, /ai-sessions, /ai-workflow | AI 对话 |
| ai-suggestions | /ai-suggestions | AI 建议中心 |
| reports | /reports | 数据分析 |
4.2 插件自描述
每个插件按 PluginManifest 接口声明自己的五要素:
// plugins/equipment/equipment.plugin.ts
export const equipmentPlugin: PluginManifest = {
id: "equipment",
version: "2.0.0",
name: "设备台账",
permissions: [ /* 该模块的所有权限码 */ ],
navItems: [ /* 前端导航声明 */ ],
approvalSupport: [{ documentType: "equipment", label: "设备信息变更" }],
lifecycleHandlers: {
"approval.approved": async (payload) => { /* 审批通过后更新设备状态 */ },
},
cronJobs: [],
createRouter: () => { /* 返回 Express Router */ },
};
4.3 PluginRegistry 核心
整个注册中心只有 74 行代码,核心逻辑极简:
// packages/core/src/plugin-registry.ts
class PluginRegistry {
private plugins = new Map<string, PluginManifest>();
register(plugin: PluginManifest): void {
// 依赖检查
for (const dep of plugin.dependencies ?? []) {
if (!this.plugins.has(dep)) throw new Error(`缺少依赖: ${dep}`);
}
this.plugins.set(plugin.id, plugin);
}
getAllPermissions(): PermissionDefinition[] { /* 聚合所有插件权限 */ }
getAllNavItems(): NavItem[] { /* 聚合导航 */ }
mountAll(router: Router): void { /* 统一挂载路由 */ }
}
4.4 启动流程
index.ts
→ app.ts (Express 实例化)
→ routes/index.ts (构建主 Router)
→ _registry.ts (注册所有插件)
→ pluginRegistry.register(coreDataPlugin)
→ pluginRegistry.register(equipmentPlugin)
→ ...
→ pluginRegistry.mountAll(mainRouter)
→ 每个插件的 createRouter() 挂载到主路由
→ 注册 AI Chat 路由、认证路由等
→ Express 中间件链
→ pino-http → cors → json → session → requireAuth → buildAbility
✅ 插件化的实际收益
新增一个业务模块(如"合同管理")只需实现 PluginManifest 接口,在 _registry.ts 中注册一行,完全不需要修改主应用代码。权限、导航、审批流自动集成。
5. AI 管道架构
5.1 管道全景
POST /api/ai/chat
│
├─ [100ms 超时] IntentAgent.preprocessIntent()
│ ├─ Step 1: 别名解析 (entity_aliases DB lookup)
│ ├─ Step 2a: 静态关键词匹配 (内存, 0ms)
│ ├─ Step 2b: 意图模式库 (intent_patterns DB)
│ └─ Step 3: LLM 兜底分类 (AI_FAST_MODEL, ~200ms)
│
└─ Orchestrator.streamOrchestrator()
├─ Phase 1: Context Build (System Prompt + Entity + Patterns)
├─ Phase 2: 图片预处理(视觉模型 → 文字摘要拼入)
├─ Phase 3: LLM Reasoning (streamText + Tool Calling)
│ ├─ Tool 注册项按 CASL 权限过滤
│ └─ Agent 执行 → UI Card 返回
└─ Phase 4: Learning (fire-and-forget 记录意图模式)
这条管道的精妙之处在于渐进式降级:0ms 关键词命中 → DB 模式匹配 → LLM 兜底。大多数常见意图在内存层就被命中,避免每次都走 LLM,成本和延迟双降。
5.2 14 个 AI Agent
| Agent | 职责 |
|---|---|
coordinator.agent.ts | 核心调度器:路由意图到子 Agent,生成 SSE 流 |
intent.agent.ts | 意图识别三层策略 + 意图学习 |
equipment.agent.ts | 设备 CRUD、360° 画像、DynamicForm 构造 |
fault.agent.ts | 故障上报、查询、分派 |
maintenance.agent.ts | 维护工单操作 |
inventory.agent.ts | 库存查询、备件推荐、出入库 |
analytics.agent.ts | 报表生成、数据统计 |
external-knowledge.agent.ts | 外部知识库 RAG 检索 |
intent-suggestion.agent.ts | 意图建议补全 |
nameplate-extractor.ts | 铭牌 OCR 两阶段管线 |
field-classifier.ts | 字段分类(纯代码查表) |
rag.ts | pgvector 检索增强生成 |
5.3 模型分层策略
AI_CHAT_MODEL=deepseek-ai/DeepSeek-V3.2 # 主推理模型 (Tool Calling)
AI_FAST_MODEL=deepseek-ai/DeepSeek-V3.2 # 意图分类、快速响应
AI_VISION_MODEL=Qwen/Qwen3-VL-32B-Instruct # 铭牌 OCR、图片识别
通过 OpenAI 兼容 API 的 baseURL 统一代理到 SiliconFlow,一行配置切换模型厂商。这个设计的战略意义是:无论是 DeepSeek、Qwen 还是未来的任何模型,都不需要改业务代码。
6. CASL 同构权限体系
6.1 架构要点
- 26 个 Subjects(Equipment, WorkOrder, FaultReport, SparePart…)
- 19 个 Actions(read, create, update, delete, dispatch, approve, grab…)
- 3 层数据范围:all / department / self
- 条件权限:动态规则如
{ requisition.amount >= 5000 } - 前后端同构:
packages/core/src/ability.ts中的buildAbilityFromPermissions()同时被 Express 中间件和 ReactAbilityProvider消费
6.2 后端权限中间件
// Express 中间件:每个请求构建 ability 挂载到 req
app.use(buildAbilityMiddleware);
// routes 中的守卫
router.post("/equipments", requireAbility("create:Equipment"), handler);
6.3 前端权限消费
// React 组件中
const ability = useAbility();
{ability.can("create", "Equipment") && <CreateButton />}
⚠️ 前后端同构的边界
前端权限判断只用于 UI 可见性控制(隐藏按钮/菜单),后端永远是真正的守门人。能在前端隐藏 UI 是体验优化,不是安全保障。
7. 通知系统:LLM 决策分发
业务事件触发 dispatch
→ 查询事件配置(是否开启)
→ 解析候选接收人(contextual-user-resolver)
→ 拉取历史通知记忆
→ LLM 决策:发给谁、发什么内容、走什么渠道
→ 渠道过滤(站内信 / 短信 / 钉钉 / 邮件)
→ 发送
关键设计:通知渠道是 NotificationDispatcher 的策略模式,新增渠道(如钉钉工作通知)只需注册新策略。让 LLM 决策"发给谁"这件事初看反直觉,但实际场景中,"工单超时该通知谁"往往依赖上下文——是通知当班人员、还是值班经理、还是设备负责人,LLM 比硬编码的规则引擎更灵活。
8. 三阶段架构重构
2026 年 5 月的架构评审诊断了 4 个系统性问题,触发了三阶段重构:
| 阶段 | 内容 | 改动量 |
|---|---|---|
| Phase 1 | Service 层引入 — 路由不再直接操作 DB,通过 Service 封装 | 14 个 Service 类 |
| Phase 2 | AI 管道去硬编码 — Coordinator 从 880 行重构为 233 行 Orchestrator + Tool Registry | coordinator.agent.ts 精简 |
| Phase 3 | MCP Server 顺手牵羊 — Tool Registry 直接输出 MCP tools/list | mcp/server.ts 66 行 |
重构的核心原则:
- P2:Service 层是唯一真相源,路由只做参数校验和调用
- P3:协调器只做编排,不做决策
- P4:LifecycleBus 是唯一副作用通道
- P5:学习优于硬编码
Phase 3 是这次重构最出乎意料的收益:因为 Tool Registry 已经把每个 AI Agent 的工具描述标准化了,只需要 66 行代码就完成了 MCP Server 的实现。EAMX 的每一个 AI 能力,现在都可以通过标准 MCP 协议暴露给外部系统(钉钉、企业微信、第三方 AI 应用),零额外工作量。
9. 核心文件索引
| 模块 | 核心文件 | 行数 |
|---|---|---|
| 架构评审 | qod/architecture-review-2026-05-08.md | 557 |
| AI 架构 | qod/ai-architecture-v2.md | 900+ |
| 插件系统 | packages/core/src/plugin-registry.ts | 74 |
| 权限 | packages/core/src/ability.ts | 132 |
| 编排器 | artifacts/api-server/src/ai/orchestrator.ts | 233 |
| 意图识别 | artifacts/api-server/src/agents/intent.agent.ts | 611 |
| 铭牌 OCR | artifacts/api-server/src/agents/nameplate-extractor.ts | 171 |
| 字段分类 | artifacts/api-server/src/agents/field-classifier.ts | 232 |
| Tool Registry | artifacts/api-server/src/ai/tool-registry.ts | 86 |
| MCP Server | artifacts/api-server/src/mcp/server.ts | 66 |
📌 本系列后续文章
后续 9 篇文章将对上述每个模块做深度拆解:从代码实现、性能优化、到踩坑复盘。下一篇将深入意图识别三层策略——如何用渐进式架构在 0ms 到 200ms 之间精准判断用户意图。