e3850f9dd8
- 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
10 KiB
10 KiB
⚙️ معماری 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
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
// 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 (مشترک)
message PaginationState {
int32 skip = 1;
int32 take = 2;
}
Namespace صحیح:
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)
// StoredProcedureDeploymentService : IHostedService
// در startup:
// 1. خواندن فایلهای .sql از embedded resource
// 2. مقایسه checksum با جدول __SPChecksums
// 3. فقط SPهای تغییریافته re-deploy میشوند
۵.۵ History Tracking System (Q27)
IHasHistory Interface
public interface IHasHistory<THistory> where THistory : BaseAuditableEntity, new()
{
THistory CreateHistorySnapshot(string action, string? performedBy);
}
HistoryTrackingSaveChangesInterceptor
- مکان:
Infrastructure/Persistence/Interceptors/HistoryTrackingSaveChangesInterceptor.cs - مکانیسم:
SaveChangesInterceptor— قبل ازSaveChangesاجرا میشود - شناسایی: از
ChangeTrackerentityهایی که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 بعد
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 ساختار
{
"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)