Files
docs/technical/TECH-01-CMS-ARCHITECTURE.md
T
masoodafar-web e3850f9dd8 docs: فاز ۱۱ — فیکس‌های پرداخت ZarinPal + تصحیح تومان/ریال + امنیت Callback URL
- CHANGELOG: Phase 11 (11a-11f) — ZarinPal verify fix, تومان/ریال مدل, صفحه موفقیت, حذف ×۱۰ دوبار, callback URL امنیت
- BUSINESS-02: تصحیح مدل ارزی (DB=تومان نه ریال), ZarinPal verify fix, جدول callback URL امنیت
- TECH-01: اضافه CmsBaseUrl/FrontOfficeBaseUrl به appsettings, توضیح امنیت Open Redirect
- TECH-02: اضافه PaymentCallback.razor, وضعیت‌های جدید
- ROADMAP: بروزرسانی Payment 97→99%, اضافه فاز ۱۱ به DONE list
2026-02-27 22:33:35 +03:30

320 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ⚙️ معماری CMS و زیرساخت فنی
> **منابع ادغام‌شده:** `CMS-README.md`, `ICURRENTUSERSERVICE-IMPLEMENTATION.md`, `FILE-MANAGEMENT-ARCHITECTURE.md`, `FRONTOFFICE-CMS-API-COMPATIBILITY.md`, `BFF-REMOVAL-PLAN.md`, `system-constants.md`
> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: فیکس ZarinPal Verify + Callback URL امنیت + appsettings.Development.json)
---
## ۱. Stack فنی
| لایه | تکنولوژی | نسخه |
|------|----------|------|
| **Runtime** | .NET | 9.0 |
| **ORM** | Entity Framework Core | 9.0 |
| **Communication** | gRPC (Protobuf) | v3 |
| **Pattern** | CQRS + MediatR | — |
| **Database** | SQL Server (MSSQL) | 2022-CU16 |
| **Job Scheduler** | Hangfire | — |
| **Auth** | JWT Bearer + Identity | — |
| **API Gateway** | حذف‌شده (Direct gRPC) | — |
---
## ۲. معماری لایه‌ای CMS
```mermaid
flowchart TD
subgraph PRES["💻 Presentation Layer"]
FO["FrontOffice\nBlazor Server"]
BO["BackOffice\nBlazor WASM"]
end
FO & BO -->|gRPC| APP
subgraph APP["⚙️ Application Layer"]
CMD["Commands\nMediatR IRequest"]
QRY["Queries\nMediatR IRequest"]
VAL["Validators\nFluentValidation"]
HND["Handlers\nIRequestHandler"]
end
APP --> DOM
subgraph DOM["📦 Domain Layer"]
ENT["Entities, Enums\nValue Objects\nDomain Events"]
end
DOM --> INF
subgraph INF["🔧 Infrastructure Layer"]
EF["EF Core DbContext"]
SVC["External Services"]
HF["Hangfire Jobs"]
FS["File Storage"]
end
INF --> DB[("🗄️ SQL Server\nSchema: CMS")]
```
---
## ۳. CQRS با MediatR
### ۳.۱ ساختار فولدرها
```
CMS/src/
├── CMSMicroservice/
│ ├── Features/
│ │ ├── Products/
│ │ │ ├── Commands/
│ │ │ │ ├── CreateProductCommand.cs
│ │ │ │ └── CreateProductCommandHandler.cs
│ │ │ ├── Queries/
│ │ │ │ ├── GetProductsQuery.cs
│ │ │ │ └── GetProductsQueryHandler.cs
│ │ │ └── Validators/
│ │ │ └── CreateProductCommandValidator.cs
│ │ ├── Orders/
│ │ ├── Users/
│ │ ├── Club/
│ │ ├── Payment/
│ │ └── Blog/
│ ├── Services/
│ │ ├── gRPC/ ← gRPC service implementations
│ │ ├── Background/ ← Hangfire jobs
│ │ └── External/ ← ZarinPal, Kavenegar, Daya, Chatika
│ ├── Infrastructure/
│ │ ├── Persistence/ ← DbContext, Migrations
│ │ └── Identity/ ← JWT, Claims, ICurrentUserService
│ └── Protos/ ← .proto files
```
### ۳.۲ مثال Command
```csharp
// Command
public record CreateProductCommand(
string Name, string Description, decimal Price,
Guid CategoryId, string ImageUrl
) : IRequest<Guid>;
// Handler
public class CreateProductCommandHandler
: IRequestHandler<CreateProductCommand, Guid>
{
private readonly CMSDbContext _db;
public async Task<Guid> Handle(
CreateProductCommand request, CancellationToken ct)
{
var product = new Product { /* map fields */ };
_db.Products.Add(product);
// Auto-create inventory record
_db.Inventories.Add(new Inventory { ProductId = product.Id });
await _db.SaveChangesAsync(ct);
return product.Id;
}
}
```
---
## ۴. gRPC Services
### ۴.۱ لیست سرویس‌ها
| سرویس | proto | متدهای اصلی |
|--------|-------|-------------|
| `ProductService` | product.proto | GetProducts, GetProduct, Create, Update, Delete |
| `OrderService` | order.proto | CreateOrder, GetOrders, UpdateStatus |
| `UserService` | user.proto | Register, Login, GetProfile, UpdateProfile |
| `ClubService` | club.proto | GetNetworkTree, GetBalance, AcceptContract |
| `PaymentService` | payment.proto | CreatePayment, VerifyPayment |
| `BlogService` | blog.proto | GetPosts, GetPost, Create, Update |
| `InventoryService` | inventory.proto | GetInventory, UpdateStock |
| `FileService` | file.proto | Upload, Download, Delete |
| `SitePageService` | sitepage.proto | GetPage, SaveSettings |
| `CategoryService` | category.proto | GetCategories, Create, Update |
| `SystemConfigService` | config.proto | GetConfig, UpdateConfig |
| `UserWalletService` | userwallet.proto | GetCustomerWallet, InitiateMagicCharge, GetMagicWalletStatus |
| `UserWalletHistoryService` | userwallethistory.proto | *(renamed from UserWalletChangeLogService)* |
### ۴.۲ PaginationState (مشترک)
```protobuf
message PaginationState {
int32 skip = 1;
int32 take = 2;
}
```
**Namespace صحیح:**
```csharp
using CMSMicroservice.Protobuf.Protos.PaginationState;
// ⚠️ نه: CMSMicroservice.Protobuf.Protos.PublicMessages.PaginationState
```
---
## ۵. Database
### ۵.۱ اتصال
```
Staging: Server=194.5.195.53; Database=FourSatCMS; Schema=CMS
Production: Server=45.149.79.127; Database=FourSatCMS; Schema=CMS
Engine: MSSQL 2022-CU16, Collation=Arabic_CI_AS
```
### ۵.۲ جداول اصلی
| جدول | توضیح | رکوردهای تقریبی |
|------|--------|----------------|
| Users | کاربران + فیلدهای شبکه (NetworkParentId, LegPosition) | ~5K |
| Products | محصولات (+ MaxDiscountPercent) | ~200 |
| Categories | دسته‌بندی‌ها | ~30 |
| Orders | سفارشات | ~2K |
| Inventories | موجودی | ~200 |
| BlogPosts | پست‌های بلاگ | ~50 |
| SitePages | صفحات سایت | ~10 |
| UserClubMemberships | عضویت باشگاه | ~500 |
| UserContracts | قراردادها (SignGuid, SignedPdfFile) | ~500 |
| UserWallets | کیف‌پول (Balance, NetworkBalance, DiscountBalance, WalletMode) | ~5K |
| ClubMembershipCycles | دوره‌های عضویت (CycleNumber, PackagePurchasedAt, IsCurrentCycle) | ~500 |
| Transactions | تراکنش‌ها | ~5K |
| SystemConfigurations | تنظیمات | ~30 |
| ChatMessages | پیام‌های چاتیکا | ~1K |
### ۵.۳ Stored Procedures
| SP | کاربرد |
|----|--------|
| `SP_GetNetworkTree` | بازگشتی — استخراج درخت باینری |
| `sp_CalculateWeeklyBalances` | محاسبه بالانس هفتگی هر عضو |
| `sp_CalculateWeeklyCommissionPool` | توزیع Pool هفتگی |
### ۵.۴ SP Auto-Deploy Worker (Q26)
```csharp
// StoredProcedureDeploymentService : IHostedService
// در startup:
// 1. خواندن فایل‌های .sql از embedded resource
// 2. مقایسه checksum با جدول __SPChecksums
// 3. فقط SP‌های تغییریافته re-deploy می‌شوند
```
---
## ۵.۵ History Tracking System (Q27)
### IHasHistory<T> Interface
```csharp
public interface IHasHistory<THistory> where THistory : BaseAuditableEntity, new()
{
THistory CreateHistorySnapshot(string action, string? performedBy);
}
```
### HistoryTrackingSaveChangesInterceptor
- **مکان:** `Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs`
- **مکانیسم:** `SaveChangesInterceptor` — قبل از `SaveChanges` اجرا می‌شود
- **شناسایی:** از `ChangeTracker` entity‌هایی که `IHasHistory<>` پیاده‌سازی کردن (Modified/Added)
- **Auto-fill:** فیلدهای `Old*` از `entry.OriginalValues` با naming convention (مثلاً `OldPrice``OriginalValues["Price"]`)
- **Entity‌های فعال:** `Package``PackageHistory`
### History Tables
| جدول | Entity مرتبط | فیلدهای Old/New |
|------|-------------|----------------|
| `PackageHistories` | Package | Price, ActivationFee, MagicMultiplier, MagicMaxDeposit, MaxBalancesPerLeg, IsActive |
| `ClubMembershipCycleHistories` | ClubMembershipCycle | IsCurrentCycle, MagicStartedAt, MagicCompletedAt |
| `UserWalletHistories` | UserWallet | *(renamed from UserWalletChangeLogs — RenameTable migration)* |
---
## ۶. حذف BFF / Gateway
### ۶.۱ قبل vs بعد
```mermaid
flowchart LR
subgraph BEFORE["قبل"]
F1["FrontOffice"] -->|REST| BFF1["BFF"]
B1["BackOffice"] -->|REST| BFF1
BFF1 -->|gRPC| C1["CMS"]
end
subgraph AFTER["بعد — فعلی ✅"]
F2["FrontOffice"] -->|gRPC| C2["CMS"]
B2["BackOffice"] -->|gRPC| C2
end
```
> مزایا: حذف لایه واسط → کاهش latency • Type-safe از proto تا UI • کاهش ۱ سرویس در deployment
### ۶.۲ سازگاری API
```
FrontOffice Service Layer:
• ProductService.cs → gRPC client wrapper
• OrderService.cs → gRPC client wrapper
• UserService.cs → gRPC client wrapper
هر Service:
• Constructor: inject GrpcChannel
• Methods: wrap gRPC calls + map to DTOs
• Error handling: try/catch RpcException
```
---
## ۷. Hangfire Jobs
| Job | فرکانس (cron) | کاربرد |
|-----|---------|--------|
| `WeeklyCommissionCalculation` | `5 0 * * 0` (یکشنبه ۰۰:۰۵) | محاسبه و توزیع کمیسیون |
| `DayaLoanProcessorJob` | `*/20 * * * *` (هر ۲۰ دقیقه) | پردازش درخواست‌های وام |
| `ChatikaAccountActivation` | `*/5 * * * *` (هر ۵ دقیقه) | فعال‌سازی حساب چاتیکا |
---
## ۸. پیکربندی
### ۸.۱ appsettings.json ساختار
```json
{
"ConnectionStrings": {
"DefaultConnection": "Server=...;Database=FourSatCMS"
},
"Jwt": {
"Secret": "***",
"Issuer": "FourSat",
"ExpiryMinutes": 1440
},
"Grpc": {
"CmsUrl": "https://localhost:5001"
},
"Hangfire": {
"DashboardPath": "/hangfire",
"WorkerCount": 4
},
"Kavenegar": { "ApiKey": "***" },
"ZarinPal": { "MerchantId": "***", "UseSandbox": true },
"DayaLoan": { "UseMock": true },
"CmsBaseUrl": "https://cms.se.kbs1.ir",
"FrontOfficeBaseUrl": "http://localhost:5268"
}
```
> **⚠️ نکات مهم appsettings:**
> - `CmsBaseUrl` — برای callback URL‌های درگاه (شارژ کیف‌پول جادویی/اعتباری)
> - `FrontOfficeBaseUrl` — برای redirect بعد پرداخت (خرید پکیج/تراکنش عمومی)
> - `appsettings.Development.json` — URL‌های localhost برای توسعه محلی
> - همه callback URL‌ها از config خوانده می‌شوند — هیچ URL از ورودی کاربر نمی‌آید (امنیت Open Redirect)