تحلیل جامع معماری، طراحی سیستم، مدل تجاری و نقشهراه پیادهسازی — قابل استفاده برای هر کسبوکاری (SaaS ابری + نصب روی سرور مشتری + ارائه بهصورت REST API)
این محصول یک پلتفرم اتوماسیون جریانکار (Workflow Automation Platform) است؛ مشابه
n8n، Make.com و Zapier، اما با تفاوت کلیدی: هسته عمومی و افقی
که با نصب «ماژول» به هر کسبوکاری تخصصی میشود. مشتری CRM میخواهد → ماژول CRM نصب میکند،
فروشگاهی است → ماژول Ecommerce، کلینیک است → ماژول نوبتدهی و پیامرسان.
┌──────────┐ ┌───────────┐ ┌────────┐ ┌───────────┐ ┌────────┐
│ Trigger │───►│ Condition │───►│ Action │───►│ Condition │───►│ Action │
│ (محرک) │ │ (شرط) │ │ (عمل) │ │ (شرط) │ │ (عمل) │
└──────────┘ └───────────┘ └────────┘ └───────────┘ └────────┘
│
├─ Webhook (دریافت داده از بیرون)
├─ Schedule/Cron (زمانبندی)
├─ Event (رویداد داخلی سیستم)
└─ Manual (اجرای دستی)
مشتری روی زیرساخت ما ثبتنام میکند، پلن میخرد، همان لحظه ماژولها فعال میشوند.
همان کدبیس روی دامنه و سرور مشتری نصب میشود (Docker). لایسنس سالانه + اتصال heartbeat.
موتور اتوماسیون بهصورت API در اختیار مشتری قرار میگیرد تا داخل نرمافزار خودش مصرف کند.
Platform/ (بیلینگ و چندمستأجری) در حالت Self-Hosted غیرفعال میشود.
یعنی نگهداری یک محصول، فروش در سه بازار.
┌──────────────────────────────────────────────────────────────────────┐ │ PLATFORM LAYER │ ├──────────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │ │ SaaS Mode │ │ Self-Hosted Mode│ │ API Gateway │ │ │ │ (Cloud) │ │ (On-Premise) │ │ (REST / Token) │ │ │ └──────┬──────┘ └────────┬─────────┘ └────────┬─────────┘ │ │ └───────────────────┼──────────────────────┘ │ │ ▼ │ │ ┌────────────────────────────┐ │ │ │ CORE ENGINE (Laravel) │ │ │ │ ┌────────────────────────┐ │ │ │ │ │ Workflow Engine │ │ │ │ │ │ Execution Engine │ │ │ │ │ │ Trigger Dispatcher │ │ │ │ │ │ Action Resolver │ │ │ │ │ │ Module Registry │ │ │ │ │ │ License Manager │ │ │ │ │ └────────────────────────┘ │ │ │ └──────────────┬─────────────┘ │ │ ┌──────────────────────┼──────────────────────┐ │ │ ▼ ▼ ▼ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ PostgreSQL / │ │ Redis / │ │ Queue Workers │ │ │ │ MySQL │ │ RabbitMQ │ │ (Laravel Horizon)│ │ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ ├──────────────────────────────────────────────────────────────────────┤ │ ADMIN PANEL (Nuxt 4 + Vue 3) │ └──────────────────────────────────────────────────────────────────────┘
[Webhook / Cron / Event]
│
▼
┌────────────────────┐
│ Trigger Controller│ ← احراز هویت، امضای HMAC، Rate Limit
└─────────┬──────────┘
▼
┌────────────────────┐
│ Check Plan & Quota │ ← آیا پلن مشتری اجازه این اجرا را میدهد؟
└─────────┬──────────┘
▼
┌────────────────────┐
│ Push to Queue │ ← ExecuteWorkflowJob (Redis)
└─────────┬──────────┘
▼
┌────────────────────┐
│ Graph Resolver │ ← Topological Sort روی DAG
└─────────┬──────────┘
▼
┌────────────────────┐
│ Node Executor(s) │ ← اجرای موازی شاخههای مستقل
└─────────┬──────────┘
▼
┌────────────────────┐
│ Execution Logger │ ← لاگ هر گره + ورودی/خروجی
└─────────┬──────────┘
▼
┌────────────────────┐
│ Usage Meter │ ← ثبت مصرف برای بیلینگ
└────────────────────┘
سیستم به سه لایه مستقل تقسیم میشود: هسته (Core)، ماژولها (Modules) و پلتفرم (Platform). هر لایه قابل تست، قابل نسخهبندی و قابل فروش جداگانه است.
┌─────────────────────────────────────────────────────────┐
│ CORE MODULES │
├─────────────────────────────────────────────────────────┤
│ automation-core │
│ ├── Workflow (تعریف جریان) │
│ ├── Node (گره: trigger/action/condition) │
│ ├── Edge (اتصال بین گرهها) │
│ ├── Execution (نمونه اجرا) │
│ ├── ExecutionContext (زمینه اجرا و متغیرها) │
│ └── VariableResolver (حل {{ path.to.value }}) │
├─────────────────────────────────────────────────────────┤
│ automation-engine │
│ ├── WorkflowExecutor ← ارکستراتور اصلی │
│ ├── NodeDispatcher ← انتخاب و اجرای گره │
│ ├── ErrorHandler ← retry / fallback / circuit break │
│ ├── ParallelExecutor ← اجرای همزمان شاخهها │
│ └── ExecutionLogger ← ثبت لاگ ساختیافته │
├─────────────────────────────────────────────────────────┤
│ automation-trigger │
│ ├── WebhookTrigger ├── ScheduleTrigger (Cron) │
│ ├── EventTrigger └── ManualTrigger │
├─────────────────────────────────────────────────────────┤
│ automation-action │
│ ├── HttpAction ├── EmailAction │
│ ├── DatabaseAction ├── WebhookAction │
│ └── CustomAction (قابل توسعه توسط ماژولها) │
└─────────────────────────────────────────────────────────┘
| اسلاگ ماژول | کاربرد | تاریخ انتشار پیشنهادی |
|---|---|---|
module-crm | لید، مخاطب، قیف فروش، پیگیری خودکار | فاز ۱ (MVP) |
module-messaging | پیامک، ایمیل، تلگرام، واتساپ | فاز ۱ |
module-ecommerce | سفارش، موجودی، سبد رهاشده | فاز ۲ |
module-payment | درگاه پرداخت، اشتراک، فاکتور | فاز ۲ |
module-approval | گردش کار تأیید چندمرحلهای | فاز ۲ |
module-data-transform | تبدیل/نقشهبرداری JSON، فرمول، تاریخ | فاز ۱ |
module-notification | اطلاعرسانی چندکاناله + قالبساز | فاز ۲ |
module-scheduler | زمانبندی پیشرفته، تقویم کاری، تعطیلات | فاز ۲ |
module-ai | گرههای شرط/عمل مبتنی بر LLM (طبقهبندی، خلاصهسازی) | فاز ۴ |
module-analytics | گزارش، داشبورد، KPI اتوماسیونها | فاز ۳ |
هر ماژول یک Laravel Package است که ۴ مورد را register میکند:
// app/Modules/Crm/ModuleServiceProvider.php class CrmServiceProvider extends ServiceProvider { public function register(): void { // ۱) گرههای قابل استفاده در Workflow NodeRegistry::register([ 'crm.contact.create' => Actions\CreateContact::class, 'crm.deal.move_stage' => Actions\MoveDealStage::class, 'crm.lead.capture' => Triggers\LeadCaptured::class, ]); // ۲) شرطهای اختصاصی ConditionRegistry::register([ 'crm.contact.exists' => Conditions\ContactExists::class, ]); // ۳) متریک مصرف (برای بیلینگ پلنمحور) UsageMeter::define('crm.contacts_created'); // ۴) منوی پنل و مجوزها AdminMenu::add('crm', 'CRM', capability: 'module.crm'); } }
با هدف ۳ مشتری اولیه → ۱۰۰ مشتری در ۲۴ ماه، ساختار قیمتگذاری باید سه لایه داشته باشد: اشتراک پلنی، فروش ماژول، و لایسنس Self-Hosted.
| # | جریان درآمدی | نوع | حاشیه سود | اولویت |
|---|---|---|---|---|
| ۱ | اشتراک ماهانه/سالانه SaaS | تکرارشونده | بالا (~۸۵٪) | فاز ۲ |
| ۲ | فروش/اجاره ماژولها | تکرارشونده + یکبار | بالا | فاز ۲ |
| ۳ | لایسنس Self-Hosted (سالانه) | تکرارشونده | بسیار بالا | فاز ۳ |
| ۴ | White-Label / OEM | قرارداد سالانه | بسیار بالا | فاز ۳ |
| ۵ | اجرای پیادهسازی و آموزش | یکبار | متوسط | فاز ۱ |
| ۶ | پشتیبانی Premium / SLA | تکرارشونده | بالا | فاز ۳ |
| ۷ | درآمد Marketplace (کمیسیون ماژول شخص ثالث) | تکرارشونده | بالا | فاز ۴ |
Tenant
│
├── Subscription ──► Plan
│ │
│ ├── plan_limits (max_workflows, runs/month, retention)
│ └── plan_modules ──┐
│ ▼
├── ModuleAccess ──────────────► آیا این ماژول برای tenant فعال است؟
│ │
│ ├── از پلن (خودکار)
│ ├── از خرید تکی (add-on)
│ └── از trial (۱۴ روزه)
│
└── UsageCounter (Redis, ریست ماهانه)
│
├── runs_this_month → اگر ≥ سقف پلن ⇒ صف متوقف + اعلان ارتقا
└── overage → محاسبه هزینه مازاد (اختیاری)
┌──────────────────────────────────────────────────┐
│ PlanGate Middleware: │
│ 1) tenant دارد؟ 2) اشتراک فعال است؟ │
│ 3) ماژول مجاز است؟ 4) سقف مصرف رد نشده؟ │
└──────────────────────────────────────────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ tenants │──1:N─►│ workspaces │──1:N─►│ workflows │
└──────┬───────┘ └──────────────┘ └──────┬───────┘
│ │
│ ┌──────────────────┼─────────────────┐
│ ▼ ▼ ▼
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
│ │workflow_nodes│ │ edges │ │ executions │
│ └──────┬──────┘ └─────────────┘ └──────┬───────┘
│ │ │
│ ┌──────▼──────┐ ┌──────▼───────┐
│ │ node_types │ │execution_logs│
│ │ (registry) │ │ (per node) │
│ └─────────────┘ └──────────────┘
│
├──1:N─► subscriptions ──N:1──► plans ──1:N──► plan_modules
│
├──1:N─► module_access ──N:1──► modules
│
├──1:N─► api_keys
├──1:N─► webhooks (endpoint دریافتی)
├──1:N─► schedules (cron definitions)
├──1:N─► usage_counters (ریست ماهانه)
├──1:N─► secrets (اعتبارنامههای رمزنگاریشده)
└──1:N─► licenses (فقط حالت Self-Hosted)
| جدول | وظیفه | نکات کلیدی |
|---|---|---|
tenants | هر شرکت/مشتری = یک tenant | دارای slug برای سابدامین و custom_domain |
workspaces | فضای کاری داخل tenant (تفکیک دپارتمان) | پیشفرض: یک workspace «Main» |
workflows | تعریف جریان | ستون graph JSON یا نرمالسازیشده در nodes/edges |
workflow_nodes | گرههای جریان | type (اسلاگ از رجیستری) + config JSON + position |
workflow_edges | اتصال گرهها | from_node, to_node, branch (true/false/default) |
executions | هر بار اجرای یک جریان | status, trigger_type, duration_ms, payload JSON |
execution_logs | لاگ در سطح گره | input/output (masked), error, attempt |
node_types | رجیستری انواع گره | از ماژولها پر میشود؛ پنل از این جدول فرم داینامیک میسازد |
modules | کاتالوگ ماژولها | slug, version, price, is_core |
module_access | کدام tenant به کدام ماژول دسترسی دارد | منبع: plan / add-on / trial + expires_at |
plans | پلنها | limits JSON (max_workflows, runs_per_month, retention_days) |
subscriptions | اشتراک فعال tenant | status: trialing/active/past_due/canceled |
licenses | لایسنس نسخه Self-Hosted | key, domain, fingerprint, expires_at |
api_keys | کلیدهای API | فقط hash ذخیره شود + prefix برای شناسایی |
secrets | توکنها و رمزهای سرویسهای بیرونی | رمزنگاری با Laravel Crypt و کلید مخصوص tenant |
usage_counters | شمارنده مصرف | ادغام Redis (سرعت) + جدول (ماندگاری و صورتحساب) |
// database/migrations/..._create_workflows_table.php Schema::create('workflows', function (Blueprint $t) { $t->id(); $t->foreignId('tenant_id')->constrained()->cascadeOnDelete(); $t->foreignId('workspace_id')->constrained()->cascadeOnDelete(); $t->string('name'); $t->string('slug'); $t->text('description')->nullable(); $t->string('status')->default('draft'); // draft|active|paused|archived $t->string('trigger_type'); // webhook|schedule|event|manual $t->json('trigger_config')->nullable(); $t->json('settings')->nullable(); // timeout, retry, concurrency $t->unsignedInteger('version')->default(1); $t->timestamp('last_run_at')->nullable(); $t->timestamps(); $t->unique(['tenant_id', 'slug']); $t->index(['tenant_id', 'status']); }); // نکته: برای مقیاس ۱۰۰ مشتری، ایندکس ترکیبی tenant_id لازم است
ساختار پیشنهادی بر پایه Domain-Driven Design سبک است: سه فضای نام اصلی
(Core، Modules، Platform) که مرزهای مسئولیت را شفاف نگه میدارند.
app/
├── Core/ ← موتور اتوماسیون (مستقل از کسبوکار)
│ ├── Workflow/
│ │ ├── Models/Workflow.php
│ │ ├── WorkflowManager.php (سرویس اصلی CRUD + نسخهبندی)
│ │ ├── WorkflowBuilder.php (DSL ساخت جریان با کد)
│ │ └── WorkflowValidator.php (اعتبارسنجی DAG، حلقهی بینهایت)
│ ├── Engine/
│ │ ├── Engine.php (ارکستراتور)
│ │ ├── GraphResolver.php (Topological Sort)
│ │ ├── NodeExecutor.php (اجرای یک گره)
│ │ ├── ContextManager.php (مسیر داده بین گرهها)
│ │ ├── ParallelExecutor.php
│ │ └── ErrorHandler.php (retry/fallback/circuit-breaker)
│ ├── Trigger/
│ │ ├── Contracts/TriggerContract.php
│ │ ├── WebhookTrigger.php
│ │ ├── ScheduleTrigger.php
│ │ ├── EventTrigger.php
│ │ └── TriggerDispatcher.php
│ └── Action/
│ ├── Contracts/ActionContract.php
│ ├── NodeRegistry.php ← رجیستری مرکزی
│ ├── HttpAction.php
│ ├── EmailAction.php
│ └── DatabaseAction.php
│
├── Modules/ ← ماژولهای تجاری (قابل خرید/نصب)
│ ├── Crm/
│ │ ├── Actions/ Triggers/ Conditions/ Migrations/
│ │ ├── Http/Controllers/
│ │ └── CrmServiceProvider.php
│ ├── Messaging/
│ ├── Ecommerce/
│ └── ...
│
├── Platform/ ← لایه تجاری (در Self-Hosted غیرفعال)
│ ├── Billing/
│ │ ├── SubscriptionManager.php
│ │ ├── PlanResolver.php (پلن فعال tenant)
│ │ └── UsageMeter.php (شمارش مصرف)
│ ├── Licensing/
│ │ ├── LicenseManager.php
│ │ └── LicenseClient.php (اتصال heartbeat به سرور ما)
│ ├── Tenancy/
│ │ ├── TenantManager.php
│ │ ├── TenantScope.php (Global Scope)
│ │ └── TenantResolver.php (سابدامین/هدر/کلید)
│ └── Marketplace/
│ ├── ModuleRegistry.php
│ └── ModuleInstaller.php
│
├── Http/
│ ├── Controllers/Api/V1/ ← REST API برای مشتریان
│ ├── Controllers/Api/Admin/ ← API پنل مدیریت پلتفرم
│ └── Middleware/
│ ├── ResolveTenant.php
│ ├── EnsurePlanFeature.php (PlanGate)
│ ├── EnsureModuleAccess.php
│ ├── EnforceQuota.php
│ └── VerifyLicense.php (فقط Self-Hosted)
│
└── Jobs/
├── ExecuteWorkflowJob.php
├── ExecuteNodeJob.php
├── DispatchScheduledWorkflowsJob.php
└── ReportUsageJob.php
// مسیرها: routes/api_v1.php Route::prefix('v1')->middleware([ 'auth:sanctum', ResolveTenant::class, EnsurePlanFeature::class, EnforceQuota::class, ])->group(function () { Route::apiResource('workflows', WorkflowController::class); Route::post('workflows/{workflow}/execute', [WorkflowController::class, 'execute']); Route::apiResource('executions', ExecutionController::class)->only(['index', 'show']); Route::get('nodes/types', [NodeTypeController::class, 'index']); // رجیستری گرهها Route::apiResource('modules', ModuleController::class)->only(['index', 'show']); Route::post('modules/{slug}/install', [ModuleController::class, 'install']); }); // Webhook عمومی (بدون Sanctum، با امضای HMAC) Route::post('v1/hooks/{token}', WebhookReceiverController::class) ->middleware(['throttle:webhooks', VerifyWebhookSignature::class]);
{
"name": "Auto Welcome New Customer",
"status": "active",
"trigger": {
"type": "webhook",
"config": {
"method": "POST",
"mapping": { "email": "$.email", "name": "$.full_name" }
}
},
"nodes": [
{ "id": "n1", "type": "action.http_request", "config": {
"url": "{{ secrets.crm_url }}/contacts",
"method": "POST",
"body": { "email": "{{ trigger.email }}" } } },
{ "id": "n2", "type": "condition.if", "config": {
"expression": "{{ n1.data.plan }} == 'premium'" } },
{ "id": "n3", "type": "action.send_email", "config": {
"to": "{{ trigger.email }}", "template": "premium_welcome" } },
{ "id": "n4", "type": "action.send_email", "config": {
"to": "{{ trigger.email }}", "template": "basic_welcome" } }
],
"edges": [
{ "from": "trigger", "to": "n1" },
{ "from": "n1", "to": "n2" },
{ "from": "n2", "to": "n3", "branch": "true" },
{ "from": "n2", "to": "n4", "branch": "false" }
]
}
interface NodeContract { public static function slug(): string; // 'action.http_request' public static function schema(): array; // فرم داینامیک پنل public static function outputSchema(): array; // برای auto-complete متغیرها public function execute(NodeContext $ctx): NodeResult; public function validate(array $config): void; // پیش از ذخیره public function retryPolicy(): RetryPolicy; }
┌───────────────────────────────────────────────────────────────┐
│ EXECUTION ALGORITHM │
├───────────────────────────────────────────────────────────────┤
│ 1) اعتبارسنجی گراف │
│ ├── آیا DAG است؟ (تشخیص حلقه با DFS) │
│ ├── آیا همه گرهها به هم متصلاند؟ (unreachable nodes) │
│ └── آیا انواع گره در رجیستری فعال tenant وجود دارد؟ │
│ │
│ 2) Topological Sort → تعیین ترتیب اجرا │
│ │
│ 3) اجرای گرهبهگره │
│ ├── Context مشترک: { trigger, nodes.{id}.data, env } │
│ ├── VariableResolver: حل {{ path.to.value }} │
│ ├── شاخههای مستقل ⇒ همزمان (Queue::batch) │
│ ├── Wait/Delay ⇒ Delayed Job │
│ └── شرط شرطی ⇒ انتخاب یال branch │
│ │
│ 4) مدیریت خطا در هر گره │
│ ├── Retry با backoff نمایی (1s, 5s, 30s) │
│ ├── Error Output Branch (مسیر جایگزین) │
│ ├── Circuit Breaker (قطع پس از N خطای متوالی) │
│ └── Continue On Fail (ادامه بدون توقف کل جریان) │
│ │
│ 5) ثبت نتیجه │
│ ├── execution_logs هر گره (input/output masked) │
│ ├── بهروزرسانی usage_counter │
│ └── رویداد WebSocket برای نمایش زنده در پنل │
└───────────────────────────────────────────────────────────────┘
class ExecuteWorkflowJob implements ShouldQueue { public function handle(Engine $engine): void { $execution = Execution::create([ 'workflow_id' => $this->workflow->id, 'status' => 'running', 'payload' => $this->payload, ]); $engine->run( workflow: $this->workflow, context: ContextManager::from($this->payload, $this->workflow->settings), execution: $execution, ); } public function failed(Throwable $e): void { // اطلاعرسانی به tenant + ثبت در monitoring } }
executions است.
| استراتژی | مزیت | عیب | مناسب برای |
|---|---|---|---|
| Single DB + tenant_id انتخابشده | ساده، ارزان، مهاجرت آسان، یک کدبیس | نیاز به انضباط در اسکوپ کوئریها | ۳ تا ۱۰۰ مشتری ✅ |
| DB-per-Tenant | ایزولهسازی کامل، بکاپ مستقل | هزینه عملیاتی بالا، مهاجرت پیچیده | +۵۰۰ مشتری یا نیازهای امنیتی سخت |
| Schema-per-Tenant | تعادل بین دو مورد قبل | پیچیدگی PostgreSQL، ابزار کمتر | Enterprise خاص |
┌────────────────────────────────────────────────────────────┐ │ TENANT RESOLUTION CHAIN │ ├────────────────────────────────────────────────────────────┤ │ ۱. Subdomain → acme.automation.ir → tenant #1 │ │ ۲. Custom Domain→ automation.acme.ir → tenant #1 │ │ ۳. API Key → ak_live_xxxx (prefix lookup) │ │ ۴. JWT Claim → tid: 1 │ │ ۵. Header → X-Tenant-Id: 1 (فقط برای Super Admin)│ │ │ │ نتیجه → app(TenantManager::class)->set($tenant) │ │ → TenantScope روی همه مدلها اعمال میشود │ └────────────────────────────────────────────────────────────┘
trait BelongsToTenant { protected static function bootBelongsToTenant(): void { // ۱) فیلتر خودکار همه کوئریها static::addGlobalScope('tenant', function (Builder $q) { if ($id = app(TenantManager::class)->id()) { $q->where($q->getModel()->getTable().'.tenant_id', $id); } }); // ۲) مقداردهی خودکار هنگام ساخت رکورد static::creating(function ($model) { if (empty($model->tenant_id)) { $model->tenant_id = app(TenantManager::class)->id(); } }); } }
tenant_id مشترک، یک باگ در اسکوپ = نشت داده بین مشتریان.
برای همین باید در فاز ۱ یک تست خودکار نوشت که برای هر مدل بررسی کند
آیا رکورد tenant دیگر از طریق API قابل خواندن است؟ (Data Leakage Test).
این بخش تعیینکنندهی مدل درآمدی پرسود محصول است. چالش اصلی: مشتری روی سرور خودش نصب میکند، پس چطور مطمئن شویم لایسنس معتبر است و از محدوده خارج نشده؟
┌───────────────────────────────────────────────────────────────┐ │ SELF-HOSTED ARCHITECTURE │ ├───────────────────────────────────────────────────────────────┤ │ │ │ سرور مشتری سرور ما (License Server) │ │ ┌────────────────┐ ┌───────────────────────┐ │ │ │ Laravel App │ HTTPS POST │ License API │ │ │ │ (همان کدبیس) │ ───────────────► │ ├─ validate(key) │ │ │ │ │ ◄─────────────── │ ├─ bind(domain,fp) │ │ │ │ LicenseMiddleware Signed JWT │ ├─ heartbeat() │ │ │ │ ▼ cache 24h │ │ └─ usage report │ │ │ │ │ POST /usage │ │ │ │ │ UsageReporter │ ───────────────► │ Metering + Billing │ │ │ └────────────────┘ └───────────────────────┘ │ │ │ │ حالت آفلاین: Grace Period ۷ روزه (لایسنس در cache) │ │ محافظت: امضای دیجیتال کد + تشخیص دستکاری فایلها │ │ توزیع: docker-compose + اسکریپت نصب یکخطی │ └───────────────────────────────────────────────────────────────┘
| مرحله | اتفاق | پاسخ سیستم در صورت خطا |
|---|---|---|
| ۱. صدور | پس از پرداخت، کلید LIC-XXXX-XXXX تولید و به مشتری ارسال میشود | — |
| ۲. فعالسازی | مشتری کلید را در پنل وارد میکند؛ کلید به domain + fingerprint قفل میشود | خطا: «لایسنس روی دامنه دیگری فعال است» |
| ۳. صدور JWT | سرور ما JWT امضاشده با کلید خصوصی برمیگرداند (اعتبار ۲۴ ساعت) | — |
| ۴. heartbeat روزانه | هر ۲۴ ساعت تمدید خودکار + ارسال آمار مصرف | تا ۷ روز: هشدار / پس از آن: حالت محدود |
| ۵. انقضا | پس از پایان دوره، حالت محدود: فقط خواندن داده و توقف اجراهای جدید | بنر اعلان تمدید در پنل |
| ۶. لغو/انتقال | مدیر میتواند قفل دامنه را آزاد کند | اجازه فعالسازی روی دامنه جدید |
class LicenseMiddleware { public function handle($request, Closure $next) { if (config('app.mode') !== 'self_hosted') { return $next($request); // در حالت SaaS بررسی لازم نیست } $license = Cache::remember('license.state', now()->addDay(), function () { return app(LicenseClient::class)->verify(); }); if (! $license->isValid() && $license->expiredMoreThan(7)) { return response()->json([ 'error' => 'license_invalid', 'message' => 'لایسنس منقضی شده است. برای تمدید با پشتیبانی تماس بگیرید.', 'renew_url' => config('license.renew_url'), ], 402); } return $next($request); } }
قرارداد اصلی: پاسخها همیشه ساختار ثابت داشته باشند، خطاها معنادار و نسخهبندی از روز اول موجود باشد.
| متد | مسیر | توضیح | محدودیت پلن |
|---|---|---|---|
| GET | /api/v1/workflows | لیست جریانها | — |
| POST | /api/v1/workflows | ساخت جریان | max_workflows |
| GET | /api/v1/workflows/{id} | جزئیات + گراف کامل | — |
| PUT | /api/v1/workflows/{id} | ویرایش جریان | — |
| DELETE | /api/v1/workflows/{id} | حذف | — |
| POST | /api/v1/workflows/{id}/execute | اجرای دستی | runs_per_month |
| POST | /api/v1/workflows/{id}/activate | فعالسازی | — |
| POST | /api/v1/workflows/{id}/duplicate | کپی جریان (قالب) | max_workflows |
| GET | /api/v1/workflows/{id}/executions | تاریخچه اجرا | retention_days |
| GET | /api/v1/executions/{id} | جزئیات اجرا | — |
| GET | /api/v1/executions/{id}/logs | لاگ سطح گره | — |
| POST | /api/v1/executions/{id}/retry | اجرای مجدد | runs_per_month |
| GET | /api/v1/nodes/types | رجیستری گرههای مجاز tenant | فیلتر بر اساس ماژول |
| GET | /api/v1/modules | کاتالوگ ماژولها | — |
| POST | /api/v1/modules/{slug}/install | نصب ماژول | modules_limit |
| DELETE | /api/v1/modules/{slug} | حذف ماژول | |
| GET | /api/v1/secrets | مدیریت اعتبارنامهها | secrets_limit |
| POST | /api/v1/hooks/{token} | دریافت وبهوک (عمومی) | rate/plan |
| GET | /api/v1/usage | مصرف ماه جاری + سقف | — |
| GET | /api/v1/plans | پلنهای قابل خرید | — |
| POST | /api/v1/subscription/checkout | شروع فرآیند پرداخت | — |
| POST | /api/v1/licenses/activate | فعالسازی لایسنس (Self-Hosted) | — |
// موفق { "data": { "id": "wf_01H...", "name": "...", "status": "active" }, "meta": { "request_id": "req_...", "tenant": "acme" } } // خطا (همیشه یک ساختار) { "error": { "code": "quota_exceeded", "message": "سقف اجرای ماهانه پلن شما تکمیل شده است.", "details": { "limit": 500, "used": 500, "resets_at": "2026-10-01T00:00:00Z" }, "upgrade_url": "https://panel.example.com/billing" } }
Sanctum برای کاربران پنلAPI Key برای سیستمهای بیرونی (با prefix قابل شناسایی)/v1 → /v2 بدون شکستن مشتریان قدیمیpanel/ (Nuxt 4)
├── app/
│ ├── pages/
│ │ ├── index.vue (داشبورد: مصرف، اجراها، خطاها)
│ │ ├── workflows/
│ │ │ ├── index.vue (لیست جریانها)
│ │ │ ├── [id].vue (★ Builder بصری)
│ │ │ └── [id]/executions.vue (تاریخچه اجرا)
│ │ ├── executions/
│ │ │ ├── index.vue
│ │ │ └── [id].vue (timeline اجرای گرهها)
│ │ ├── marketplace/index.vue (کاتالوگ + خرید ماژول)
│ │ ├── secrets/index.vue (مدیریت اعتبارنامهها)
│ │ ├── api-keys/index.vue
│ │ ├── billing/
│ │ │ ├── plans.vue
│ │ │ └── invoices.vue
│ │ ├── settings/ (کاربران، نقشها، دامنه)
│ │ └── admin/ (★ فقط Super Admin پلتفرم)
│ │ ├── tenants.vue (مدیریت مشتریان)
│ │ ├── licenses.vue
│ │ ├── modules.vue (انتشار ماژول)
│ │ └── revenue.vue (MRR, Churn, Usage)
│ ├── components/
│ │ ├── builder/ ← ★ بخش حیاتی پروژه
│ │ │ ├── FlowCanvas.vue (Vue Flow + کانواس)
│ │ │ ├── NodePalette.vue (لیست گرهها، جستوجو)
│ │ │ ├── NodeConfigPanel.vue (فرم داینامیک از schema)
│ │ │ ├── VariablePicker.vue (انتخاب {{ }} با autocomplete)
│ │ │ ├── TestRunPanel.vue (اجرای آزمایشی + نمایش نتیجه)
│ │ │ └── TriggerConfig.vue
│ │ └── ui/ (دکمه، جدول، مودال، Toast)
│ ├── composables/
│ │ ├── useApi.ts (لایه fetch + retry + خطا)
│ │ ├── useWorkflow.ts
│ │ ├── useNodeRegistry.ts
│ │ ├── useUsage.ts
│ │ └── useRealtime.ts (Laravel Echo / WebSocket)
│ ├── stores/
│ │ ├── auth.ts (Pinia)
│ │ ├── tenant.ts
│ │ ├── workflowBuilder.ts (state کانواس)
│ │ └── notifications.ts
│ └── middleware/
│ ├── auth.ts
│ └── planGuard.ts (قفل UI بر اساس پلن)
│
└── nuxt.config.ts
| صفحه | کاربر | امکانات کلیدی |
|---|---|---|
| داشبورد | مشتری | مصرف ماه، نرخ موفقیت اجراها، خطاهای اخیر، مصرف به تفکیک ماژول |
| Workflow Builder | مشتری | Drag&Drop، اتصال گرهها، فرم داینامیک، اجرای آزمایشی، پیکربندی شرطها |
| تاریخچه اجرا | مشتری | فیلتر بر اساس وضعیت، timeline گرهبهگره، مشاهده ورودی/خروجی هر گره، retry |
| Marketplace | مشتری | مرور ماژولها، پیشنمایش گرههای هر ماژول، نصب/خرید، Trial ۱۴ روزه |
| اعتبارنامهها | مشتری | ذخیره امن توکن سرویسهای بیرونی + تست اتصال |
| کلیدهای API | مشتری | ساخت/لغو کلید، انتخاب Scope، مشاهده آخرین استفاده |
| صورتحساب | مشتری | پلن فعلی، ارتقا/تغییر، فاکتورها، افزودن ماژول (add-on) |
| مدیریت مشتریان | Super Admin | ساخت tenant، تعلیق، تغییر پلن دستی، ورود به پنل مشتری (impersonate) |
| لایسنسها | Super Admin | صدور کلید، قفل/آزادسازی دامنه، مشاهده heartbeat |
| درآمد و متریک | Super Admin | MRR، ARR، Churn، ماژولهای پرفروش، مصرف کل |
با هدف ۳ مشتری در ۶ ماه اول و ۱۰۰ مشتری در ۲۴ ماه، اولویتبندی بر پایه «اول درآمد، بعد مقیاس» است.
webhook trigger, schedule trigger, http request, if condition, send email{{ trigger.x }} با پشتیبانی نقطهگذاری)plans, subscriptions, PlanGate Middlewaremodule-crm و module-messaging| موضوع | راهحل انتخابی | دلیل |
|---|---|---|
| اجرای جریان | Laravel Queue + Horizon + Job Chaining | مقیاسپذیر، قابل مانیتور، آشنای Laravel |
| دریافت وبهوک | Endpoint عمومی + امضای HMAC + Rate Limit | امنیت و جلوگیری از سوءاستفاده |
| ارزیابی شرطها | symfony/expression-language در Sandbox محدود | قدرتمند، امنتر از eval، قابل محدودسازی |
| حل متغیرها | موتور قالب سفارشی {{ path.to.value }} | سادگی برای کاربر غیرفنی + کنترل کامل |
| پیمایش گراف | Topological Sort + Visitor Pattern | تشخیص حلقه، اجرای موازی، قابل تست |
| چندمستأجری | Single DB + tenant_id + Global Scope | سادگی عملیاتی برای ۱۰۰ مشتری |
| اعتبارسنجی لایسنس | JWT امضاشده + heartbeat روزانه + Grace ۷ روز | تعادل بین محافظت و رضایت مشتری |
| نصب ماژول | Laravel Package Discovery + Service Provider | بدون تغییر هسته، قابل نسخهبندی |
| محدودیت نرخ | Redis + Token Bucket per-tenant | محافظت از پلتفرم در برابر مشتری پرمصرف |
| ایزولهسازی اجرا | Context مستقل per execution + عدم state مشترک | جلوگیری از نشت داده بین اجراها |
| Secrets | Laravel Crypt + کلید مجزا per tenant | عدم ذخیره متن خام اعتبارنامهها |
| مانیتور زنده | Laravel Reverb (WebSocket) | Self-Hosted friendly، بدون وابستگی به سرویس خارجی |
| صیانت داده اجرا | Masking مقادیر حساس + پاکسازی دورهای per retention | کاهش ریسک امنیتی و هزینه ذخیرهسازی |
| پایداری اجرای طولانی | Timeout per node + Kill Switch جریان | جلوگیری از اشغال بیپایان worker |
| Idempotency | هدر Idempotency-Key + ذخیره در Redis | جلوگیری از اجرای تکراری وبهوک |
هر ماژول گرههای جدید اضافه میکند و پیکربندی پیچیده میشود.
راهکار: رجیستری متمرکز node_types با schema استاندارد + دستهبندی و جستوجو در پالت پنل.
مشتری نمیفهمد کجا خطا خورده است.
راهکار: Timeline اجرای گرهبهگره + نمایش ورودی/خروجی واقعی + «اجرای گامبهگام» در Builder + Test Payload ذخیرهشده.
ویرایش یک جریان فعال ممکن است اجراها را بشکند.
راهکار: جریان «پیشنویس» جدا از «منتشرشده» + شماره نسخه + امکان بازگشت.
خطر جدا شدن کدبیس و دوبارهکاری.
راهکار: یک ریپو، پرچم app.mode، تمام کدهای بیلینگ در Platform/ که در حالت Self-Hosted بارگذاری نمیشود.
انتخابشده: MONOREPO ├── packages/ │ ├── core/ (موتور اتوماسیون — قابل استفاده در همه حالتها) │ ├── modules/ (ماژولهای تجاری) │ └── platform/ (بیلینگ، چندمستأجری، لایسنس) ├── apps/ │ ├── api/ (Laravel) │ └── panel/ (Nuxt) └── docker/ (بسته Self-Hosted) مزیت: یک کدبیس ⇒ SaaS و Self-Hosted هر دو از یک منبع عیب: نیاز به انضباط در جداسازی لایهها (تستهای مرزی)
workflow_nodes و workflow_edges ← منبع حقیقتgraph JSON فقط بهعنوان کش/خروجی برای پنل و API| حالت | مناسب برای | پاسخ API |
|---|---|---|
| Async پیشفرض | همه جریانها، مخصوصاً چندگرهای و طولانی | 202 Accepted + execution_id |
| Sync (اختیاری) | جریانهای سبک که مشتری منتظر پاسخ است | 200 OK + داده خروجی (timeout سخت ۵ ثانیه) |
| Hybrid | مشتری با ?mode=wait&timeout=10 تعیین میکند | تکمیل در زمان مشخص ⇒ 200، وگرنه 202 |
{{ trigger.email }}
ساده، امن، برای کاربر غیرفنی. لایه پیشفرض
trigger.amount > 1000 and trigger.plan == "vip"
برای شرطهای پیچیده، در Sandbox. لایه پیشرفته
منعطف ولی پرخطر. فقط Enterprise با sandbox ایزوله
تصمیم: Docker Compose بهعنوان روش رسمی؛ ZIP فقط در صورت درخواست مشتری و با هزینه پیادهسازی.
| لایه | تکنولوژی | مسئولیت |
|---|---|---|
| موتور اتوماسیون | Laravel 11 / PHP 8.3 | اجرای جریانها، گراف، Context، مدیریت خطا |
| API | Laravel REST + Sanctum + API Key | ارتباط پنل و سیستمهای مشتری |
| صف و اجرا | Redis + Laravel Horizon | اجرای Async، retry، مانیتورینگ |
| دیتابیس | PostgreSQL (یا MySQL) | داده اصلی + JSON برای پیکربندیها |
| پنل مدیریت | Nuxt 4 + Vue 3 + Pinia | Builder بصری، پنل مشتری، پنل Super Admin |
| کانواس جریان | @vue-flow/core | Drag&Drop، اتصال گرهها، فرم داینامیک |
| WebSocket | Laravel Reverb | نمایش زنده اجرا و رویدادها |
| Self-Hosted | Docker Compose | نصب روی سرور مشتری |
| لایسنس | JWT امضاشده + heartbeat | کنترل نسخه Self-Hosted |
| چندمستأجری | Single DB + tenant_id + Global Scope | جداسازی داده مشتریان |
| ماژولها | Laravel Packages + Module Registry | تخصصیسازی برای هر صنعت |
| بیلینگ | پلن + Usage Metering + درگاه پرداخت | درآمد تکرارشونده |
app.mode و لایه Platform/.پیشنهاد میکنم به ترتیب زیر پیش برویم و برای هر بخش یک فایل تحلیل جداگانه بسازیم:
module-crm): گرهها، فرمها، سناریوهای واقعیNodeContract و ساختار schema استاندارد| # | سؤال | تأثیر |
|---|---|---|
| ۱ | بازار هدف اولیه کدام صنعت است؟ (CRM / فروشگاه / کلینیک / ...) | ماژول اول، پیام بازاریابی، قالبهای آماده |
| ۲ | درگاه پرداخت داخلی (زرینپال/آیدیپی) یا بینالمللی (Stripe) یا هر دو؟ | معماری Billing و صورتحساب |
| ۳ | پنل Builder در فاز ۱ کامل باشد یا ساده (JSON Editor + پیشنمایش)؟ | زمان MVP — تفاوت ۲ تا ۳ هفته |
| ۴ | نسخه Self-Hosted از ابتدا یا فاز ۳؟ | پیچیدگی معماری و زمانبندی |
| ۵ | مجوز ماژولهای شخص ثالث: فقط داخلی یا Marketplace باز؟ | طراحی فرآیند انتشار و بررسی |