从隐式变量传递到显式合约驱动 — 2026-03-01
前端 template-variables.ts
appointment.date appointment.time guests[].name services[].price technician.name
后端 Seed migration 1807000000012
{{appointment_date}}
{{appointment_time}}
{{customer_name}}
{{service_name}}
{{employee_name}}
前端定义的是嵌套结构(appointment.date),后端 seed 模板用的是扁平命名(appointment_date)。两者不兼容。
要知道 "appointment confirmation 模板能用哪些变量",必须:
execution-processor.js 的 _buildFullVariableContext()service-context.js 的 buildContext()template-variables.ts 的前端定义三个文件分散定义,没有编译时或运行时校验它们是否一致。
| 编辑器 | 现状 | 问题 |
|---|---|---|
| HTML Editor | 原始 HTML + 组件库 | 非技术用户门槛过高 |
| Canvas Editor (Fabric.js) | 拖拽式可视化 | 主要用于 Email Designs,变量插入靠剪贴板 |
| GrapesJS Visual Editor | Spec 070,Draft | 尚未完成,变量支持为 MVP 级别 |
| 层 | 职责 | 关键输出 |
|---|---|---|
| Layer 1 Event Type | 定义"发生了什么"(信号),携带 seed data | { appointmentId, guestId, eventTime } |
| Layer 2 Automation Rule | 定义"何时发、发给谁、用哪个模板"(调度 + 路由) | 调度时间、通道、受众、模板引用 |
| Layer 3 Variable Schema | SSOT:声明式定义每种模板类型可用的变量集 | 变量名、类型、是否数组、示例值、描述 |
| Layer 4 Content Template | 使用 Variable Schema 内的变量渲染内容 | 渲染后的 HTML / 纯文本 |
{
"schema_type": "service_email",
"description": "用于预约相关通知(确认、提醒、取消等)",
"variables": {
"appointment": {
"type": "object",
"fields": {
"id": { "type": "string", "example": "APT-20260301-001" },
"date": { "type": "string", "example": "2026-03-15", "format": "date" },
"time": { "type": "string", "example": "14:00" },
"status": { "type": "string", "example": "confirmed", "enum": ["confirmed","cancelled","completed"] },
"total": { "type": "number", "example": 85.00, "format": "currency" }
}
},
"guest": {
"type": "object",
"fields": {
"name": { "type": "string", "example": "Jane Smith" },
"email": { "type": "string", "example": "jane@example.com" },
"phone": { "type": "string", "example": "+1234567890" }
}
},
"services": {
"type": "array",
"item_fields": {
"name": { "type": "string", "example": "Gel Manicure" },
"price": { "type": "number", "example": 45.00, "format": "currency" },
"duration": { "type": "number", "example": 60, "unit": "minutes" }
}
},
"technician": {
"type": "object",
"fields": {
"name": { "type": "string", "example": "Emily" },
"phone": { "type": "string", "example": "+1234567890" }
}
},
"store": {
"type": "object",
"fields": {
"name": { "type": "string", "example": "QQ Nails Flushing" },
"address": { "type": "string", "example": "123 Main St" },
"phone": { "type": "string", "example": "+1234567890" }
}
},
"system": {
"type": "object",
"fields": {
"cancel_link": { "type": "string", "example": "https://..." },
"reschedule_link": { "type": "string", "example": "https://..." },
"calendar_url": { "type": "string", "example": "https://..." }
}
}
},
"resolver": "service-context",
"seed_fields": ["appointmentId", "guestId"]
}
| Schema Type | 适用场景 | 核心变量 | Resolver |
|---|---|---|---|
service_email |
预约确认、提醒、取消、改期 | appointment, guest, services[], technician, store, system | service-context.js |
promotion_email |
营销活动、折扣推广 | guest (含 loyalty), store, campaign, system | promotion-context.js |
lifecycle_email |
生日祝福、流失挽回 | guest (含 visit history), store, system | lifecycle-context.js (待建) |
transactional_email |
支付收据、退款通知、礼品卡 | guest, transaction, store, system | transactional-context.js (待建) |
| 环节 | 现状(隐式) | 目标(显式) |
|---|---|---|
| 变量定义 | 前端 ts + 后端 context builder + seed 模板 三处各自定义 |
Variable Schema JSON 为 SSOT 前后端从中派生 |
| 变量命名 | 嵌套 vs 扁平不一致 | 统一嵌套命名 appointment.date |
| 编辑器变量发现 | 前端硬编码 template-variables.ts |
从 Schema 动态加载 按 schema_type 过滤 |
| 渲染前校验 | 无(missing 变量静默变空字符串) | Resolver 输出对照 Schema 校验 缺失变量 warning log |
| 调度时间 | 绑定在 trigger_templates 表 | 属于 Automation Rule(同一事件可多种调度) |
无论哪种形式,都共享同一个 Variable Schema:
| 形式 | 当前体验 | 改进方向 |
|---|---|---|
| HTML Editor | 手动输入 {{var}} |
VariableCascader + 点击插入(已有,需对齐 Schema) |
| Visual Editor | 剪贴板复制粘贴 | inline variable chip(选中文本块 → 插入变量 → 显示为可视化标签) |
| Plain Text | 不存在 | 简单文本区 + 变量列表侧边栏 |
| 方案 | 优点 | 缺点 |
|---|---|---|
A. 代码中 JSON 文件backend/schemas/variable-schemas/*.json |
版本控制、代码审查、简单 | 新增 schema 需部署 |
B. 数据库表variable_schemas |
运行时可修改、租户可自定义 | 增加复杂度、需要管理界面 |
| C. 混合 系统 schema 在代码,自定义在 DB |
平衡灵活性和可控性 | 两个数据源需要合并逻辑 |
当前 trigger_templates (public schema) 定义了事件类型 + 调度时间。重设计后调度时间属于 Automation Rule。
| 方案 | 说明 |
|---|---|
| A. 保留 trigger_templates | 作为"事件类型注册表",去掉调度字段,移到 automation_rules |
| B. 合并到 automation_rules | trigger_templates 功能全部吸收进 automation_rules |
现有 seed 模板使用扁平命名({{store_name}}),需要迁移到嵌套命名({{store.name}})。
| 方案 | 说明 |
|---|---|
| A. 一次性迁移 | 更新所有 seed 模板 + context builder 输出 |
| B. 兼容层 | context builder 同时输出嵌套和扁平两种格式,逐步迁移 |
| 组件 | 文件 |
|---|---|
| 前端变量定义 | frontend/web_app/src/lib/template-variables.ts |
| 前端变量选择器 | frontend/web_app/src/components/TemplateEditor/VariableCascader.tsx |
| 后端变量引擎 | backend/services/templating/variable-engine.js |
| Service 上下文构建器 | backend/services/templating/context-builders/service-context.js |
| Promotion 上下文构建器 | backend/services/templating/context-builders/promotion-context.js |
| 自动化执行处理器 | backend/services/automation/execution-processor.js |
| 触发引擎 | backend/services/automation/trigger-engine.js |
| 邮件发送 | backend/services/email/email-sender.js |
| HTML 模板编辑器 API | backend/api/email-template-builder.js |
| Email Designs API | backend/api/email-designs.js |
| Canvas 可视化编辑器 | frontend/web_app/src/components/EmailBuilder/CanvasEditor/ |
| Seed 模板迁移 | backend/database/migrations/1807000000012_seed-appointment-email-templates.js |
| Spec | 状态 | 关系 |
|---|---|---|
018-marketing-automation | Draft | 定义了触发引擎、自动化规则、调度系统 |
070-visual-email-editor | Draft | GrapesJS 可视化编辑器,变量插入为 MVP |
001-email-sms-integration | Done | 邮件/SMS 发送基础设施 |
更新日期: 2026-03-01