20 KiB
20 KiB
Kiến Trúc Mission Service
Tài liệu kiến trúc kỹ thuật cho Mission Service.
📖 Xem thêm: README - Tổng Quan Dịch Vụ
Kiến Trúc Tổng Quan
%%{init: {'theme':'dark'}}%%
graph TD
subgraph API["🌐 Tầng API"]
Controllers[Controllers]
Commands[Commands]
Queries[Queries]
end
subgraph Domain["⚙️ Tầng Domain"]
Mission[Mission Aggregate]
Task[Task Aggregate]
CheckIn[CheckIn Aggregate]
end
subgraph Infra["💾 Tầng Infrastructure"]
EF[EF Core]
Redis[(Redis Cache)]
RabbitMQ[RabbitMQ]
end
subgraph Data["🗄️ Lưu Trữ Dữ Liệu"]
DB[(PostgreSQL)]
end
API --> Domain
Domain --> Infra
EF --> DB
style API fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:3px
style Domain fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:2px
style Infra fill:#34495E,color:#ECF0F1,stroke:#2C3E50,stroke-width:2px
style Data fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
Các Mẫu Kiến Trúc
CQRS Pattern
%%{init: {'theme':'dark'}}%%
flowchart LR
subgraph Write["📝 Commands (Ghi)"]
C1[StartTaskCommand]
C2[CompleteTaskCommand]
C3[ClaimRewardCommand]
C4[CheckInCommand]
C5[CreateMissionCommand]
end
subgraph Read["📖 Queries (Đọc)"]
Q1[GetMissionsQuery]
Q2[GetUserTasksQuery]
Q3[GetCheckInStatusQuery]
Q4[GetAnalyticsQuery]
end
style Write fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
style Read fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
Domain Model
Mission Aggregate
%%{init: {'theme':'dark'}}%%
classDiagram
class Mission {
+Guid Id
+string Code
+LocalizedString Title
+LocalizedString Description
+MissionType Type
+MissionCategory Category
+MissionReward Reward
+FrequencyType Frequency
+int MaxCompletions
+DateTime StartDate
+DateTime EndDate
+MissionStatus Status
+int Priority
+Activate()
+Pause()
+ValidateCompletion()
}
class MissionRule {
+Guid Id
+string RuleType
+string Operator
+string Value
+string Metadata
}
class MissionReward {
+decimal Points
+decimal MiningBoost
+int ExperiencePoints
+string BadgeId
}
Mission "1" --> "*" MissionRule : chứa
Mission --> MissionReward : sử dụng
Task Aggregate
%%{init: {'theme':'dark'}}%%
classDiagram
class UserTask {
+Guid Id
+Guid UserId
+Guid MissionId
+TaskStatus Status
+TaskProgress Progress
+TaskEvidence Evidence
+VerificationResult Verification
+bool RewardClaimed
+DateTime StartedAt
+DateTime CompletedAt
+Start()
+UpdateProgress()
+SubmitEvidence()
+Complete()
+ClaimReward()
}
class TaskProgress {
+int CurrentValue
+int TargetValue
+decimal PercentComplete
+DateTime LastUpdated
}
class TaskEvidence {
+string EvidenceType
+string Data
+string ScreenshotUrl
+DateTime CapturedAt
}
class VerificationResult {
+bool IsValid
+string FailureReason
+VerificationMethod Method
+Guid VerifiedBy
+DateTime VerifiedAt
}
UserTask --> TaskProgress : theo dõi
UserTask --> TaskEvidence : có
UserTask --> VerificationResult : xác thực bởi
CheckIn Aggregate
%%{init: {'theme':'dark'}}%%
classDiagram
class UserCheckIn {
+Guid Id
+Guid UserId
+int CurrentStreak
+int LongestStreak
+int TotalCheckIns
+DateOnly LastCheckInDate
+CheckIn()
+CanCheckInToday()
+GetStreakBonus()
+GetMonthlyReward()
}
class CheckInDay {
+Guid Id
+DateOnly Date
+decimal PointsEarned
+bool IsMilestone
}
class StreakBonus {
+int MinDays
+int MaxDays
+decimal BonusPercent
+decimal MilestoneBonus
}
UserCheckIn "1" --> "*" CheckInDay : ghi nhận
UserCheckIn --> StreakBonus : áp dụng
Reward Aggregate
%%{init: {'theme':'dark'}}%%
classDiagram
class UserReward {
+Guid Id
+Guid UserId
+Guid SourceId
+RewardType Type
+RewardStatus Status
+RewardAmount Amount
+DateTime EarnedAt
+DateTime ClaimedAt
+Claim()
+Expire()
}
class RewardAmount {
+decimal Points
+decimal BonusPoints
+string Currency
+TotalPoints()
}
class RewardType {
<<enumeration>>
MISSION_COMPLETE
CHECKIN_DAILY
CHECKIN_MILESTONE
REFERRAL_BONUS
SOCIAL_ACTION
}
UserReward --> RewardAmount : has
UserReward --> RewardType : uses
Value Objects
MissionReward
public record MissionReward : ValueObject
{
public decimal Points { get; init; }
public decimal MiningBoostPercent { get; init; }
public int ExperiencePoints { get; init; }
public string? BadgeId { get; init; }
public static MissionReward Create(
decimal points,
decimal miningBoost = 0,
int xp = 0,
string? badge = null)
{
Guard.Against.Negative(points, nameof(points));
Guard.Against.Negative(miningBoost, nameof(miningBoost));
return new MissionReward
{
Points = points,
MiningBoostPercent = miningBoost,
ExperiencePoints = xp,
BadgeId = badge
};
}
}
TaskProgress
public record TaskProgress : ValueObject
{
public int CurrentValue { get; init; }
public int TargetValue { get; init; }
public DateTime LastUpdated { get; init; }
public decimal PercentComplete =>
TargetValue > 0
? Math.Min(100, (CurrentValue * 100m) / TargetValue)
: 0;
public bool IsComplete => CurrentValue >= TargetValue;
public TaskProgress UpdateProgress(int newValue)
{
return this with
{
CurrentValue = Math.Min(newValue, TargetValue),
LastUpdated = DateTime.UtcNow
};
}
}
TaskEvidence
public record TaskEvidence : ValueObject
{
public EvidenceType Type { get; init; }
public string Data { get; init; } = string.Empty;
public string? ScreenshotUrl { get; init; }
public string? VideoUrl { get; init; }
public DateTime CapturedAt { get; init; }
public enum EvidenceType
{
WatchDuration, // Video xem được bao lâu
ClickData, // Thông tin click
UploadedContent, // URL nội dung tải lên
SocialProof, // Screenshot hành động xã hội
InviteCode // Mã mời đã sử dụng
}
}
Database Schema
ER Diagram
%%{init: {'theme':'dark'}}%%
erDiagram
Mission ||--o{ MissionRule : có
Mission ||--o{ UserTask : tạo ra
UserTask }o--|| User : thuộc về
User ||--o| UserCheckIn : có
UserCheckIn ||--o{ CheckInDay : ghi nhận
Mission {
uuid Id PK
string Code UK
json Title
json Description
string Type
string Category
json Reward
string Frequency
int MaxCompletions
datetime StartDate
datetime EndDate
string Status
int Priority
datetime CreatedAt
}
MissionRule {
uuid Id PK
uuid MissionId FK
string RuleType
string Operator
string Value
json Metadata
}
UserTask {
uuid Id PK
uuid UserId FK
uuid MissionId FK
string Status
json Progress
json Evidence
json Verification
bool RewardClaimed
datetime StartedAt
datetime CompletedAt
}
UserCheckIn {
uuid Id PK
uuid UserId FK
int CurrentStreak
int LongestStreak
int TotalCheckIns
date LastCheckInDate
}
CheckInDay {
uuid Id PK
uuid UserCheckInId FK
date Date
decimal PointsEarned
bool IsMilestone
}
Luồng Nhận Thưởng với Wallet Service
Luồng Nhận Thưởng Task
%%{init: {'theme':'dark'}}%%
sequenceDiagram
participant Client as 📱 Client
participant API as 🌐 Mission API
participant Task as ⚙️ TaskAggregate
participant DB as 💾 PostgreSQL
participant MQ as 📨 RabbitMQ
participant Wallet as 💰 Wallet Service
Client->>API: POST /api/v1/tasks/{id}/claim
API->>Task: ClaimRewardCommand
Task->>Task: ValidateTaskCompleted()
Task->>Task: ValidateNotAlreadyClaimed()
alt Task Hợp Lệ
Task->>Task: MarkRewardClaimed()
Task->>DB: SaveChanges()
Task->>MQ: Publish MissionCompletedEvent
MQ->>Wallet: Consume MissionCompletedEvent
Wallet->>Wallet: GrantPoints(userId, amount)
API-->>Client: 200 OK { points_earned }
else Đã Nhận Thưởng
API-->>Client: 400 Bad Request
end
Luồng Thưởng Check-in
%%{init: {'theme':'dark'}}%%
sequenceDiagram
participant Client as 📱 Client
participant API as 🌐 Mission API
participant CheckIn as 📅 CheckInAggregate
participant DB as 💾 PostgreSQL
participant MQ as 📨 RabbitMQ
participant Wallet as 💰 Wallet Service
Client->>API: POST /api/v1/checkin
API->>CheckIn: CheckInCommand
CheckIn->>CheckIn: CanCheckInToday()
alt Có Thể Check-in
CheckIn->>CheckIn: IncrementStreak()
CheckIn->>CheckIn: CalculateStreakBonus()
CheckIn->>CheckIn: CheckMilestone()
CheckIn->>DB: SaveCheckInDay()
CheckIn->>MQ: Publish CheckInCompletedEvent
MQ->>Wallet: Consume CheckInCompletedEvent
Wallet->>Wallet: GrantPoints(userId, dailyPoints + bonus)
API-->>Client: 200 OK { streak, points, milestone }
else Đã Check-in Hôm Nay
API-->>Client: 400 Đã điểm danh hôm nay
end
Integration Events
Các Event Được Xuất Bản
%%{init: {'theme':'dark'}}%%
flowchart LR
subgraph Mission["📋 Mission Service"]
M[Events Publisher]
end
subgraph Events["📨 Integration Events"]
E1[MissionCompletedEvent]
E2[CheckInCompletedEvent]
E3[TaskStartedEvent]
E4[RewardClaimedEvent]
end
subgraph Consumers["📥 Người Tiêu Thụ"]
Wallet[Wallet Service]
Mining[Mining Service]
end
M --> E1 --> Wallet
M --> E2 --> Wallet
M --> E3
M --> E4 --> Wallet
E1 -.-> Mining
style Events fill:#E67E22,color:#ECF0F1,stroke:#D35400,stroke-width:2px
style Wallet fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
Các Event Được Tiêu Thụ
%%{init: {'theme':'dark'}}%%
flowchart RL
subgraph Publishers["📤 Nhà Xuất Bản"]
IAM[IAM Service]
Mining[Mining Service]
end
subgraph Events["📨 Integration Events"]
E1[UserRegisteredEvent]
E2[UserDeletedEvent]
E3[ReferralActivatedEvent]
end
subgraph Mission["📋 Mission Service"]
M[Events Consumer]
end
IAM --> E1 --> M
IAM --> E2 --> M
Mining --> E3 --> M
style Events fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
style Mission fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:3px
Event Payloads
// Events Xuất Bản
public record MissionCompletedEvent(
Guid TaskId,
Guid UserId,
Guid MissionId,
string MissionType,
decimal PointsEarned, // Điểm kiếm được
DateTime CompletedAt
);
public record CheckInCompletedEvent(
Guid UserId,
int StreakDays, // Số ngày streak
decimal BasePoints, // Điểm cơ bản
decimal StreakBonus, // Thưởng streak
decimal MilestoneBonus, // Thưởng mốc
decimal TotalPoints, // Tổng điểm
DateTime CheckedInAt
);
public record RewardClaimedEvent(
Guid TaskId,
Guid UserId,
decimal PointsAmount,
string RewardType,
DateTime ClaimedAt
);
// Events Tiêu Thụ từ IAM
public record UserRegisteredEvent(
Guid UserId,
string Email,
string DisplayName,
DateTime RegisteredAt
);
public record UserDeletedEvent(
Guid UserId,
DateTime DeletedAt
);
// Events Tiêu Thụ từ Mining
public record ReferralActivatedEvent(
Guid InviterId,
Guid InviteeId,
string InviteCode,
DateTime ActivatedAt
);
Tích Hợp IAM Service
Luồng Xác Thực JWT
%%{init: {'theme':'dark'}}%%
sequenceDiagram
participant Client as 📱 Client
participant API as 🌐 Mission API
participant Auth as 🔐 JWT Middleware
participant IAM as 👤 IAM Service
Client->>API: Request + Bearer Token
API->>Auth: Validate Token
Auth->>Auth: Verify Signature (public key)
Auth->>Auth: Check Expiration
Auth->>Auth: Extract Claims
alt Token Hợp Lệ
Auth->>API: UserId, Roles, Permissions
API->>API: Xử lý Request
API-->>Client: 200 OK Response
else Token Không Hợp Lệ
Auth-->>Client: 401 Unauthorized
else Token Hết Hạn
Auth-->>Client: 401 Token Expired
end
Handler UserRegisteredEvent
public class UserRegisteredEventConsumer : IConsumer<UserRegisteredEvent>
{
private readonly IMissionDbContext _dbContext;
private readonly ILogger<UserRegisteredEventConsumer> _logger;
public async Task Consume(ConsumeContext<UserRegisteredEvent> context)
{
var @event = context.Message;
// Tạo hồ sơ check-in cho user mới
var userCheckIn = new UserCheckIn(
userId: @event.UserId,
currentStreak: 0,
longestStreak: 0,
totalCheckIns: 0
);
_dbContext.UserCheckIns.Add(userCheckIn);
await _dbContext.SaveChangesAsync();
_logger.LogInformation(
"Created check-in profile for new user {UserId}",
@event.UserId);
}
}
IAM Service Client
public interface IIamServiceClient
{
Task<UserInfo?> GetUserInfoAsync(Guid userId, CancellationToken ct);
Task<List<UserInfo>> GetUsersByIdsAsync(List<Guid> userIds, CancellationToken ct);
}
public record UserInfo(
Guid Id,
string Email,
string DisplayName,
string? AvatarUrl,
DateTime CreatedAt
);
Tích Hợp Storage Service
Luồng Upload Nội Dung (Pay Per Upload)
%%{init: {'theme':'dark'}}%%
sequenceDiagram
participant Client as 📱 Client
participant API as 🌐 Mission API
participant Storage as 📦 Storage Service
participant AI as 🤖 Content Moderation
participant DB as 💾 PostgreSQL
Client->>API: POST /tasks/{id}/upload
API->>API: Validate Task Status
API->>Storage: POST /api/v1/upload (multipart)
Storage->>Storage: Validate File (type, size)
Storage->>Storage: Store File
Storage-->>API: { fileId, url, metadata }
API->>AI: Moderate Content (async)
API->>DB: Save TaskEvidence { fileUrl }
API-->>Client: 202 Accepted { evidenceId }
Note over AI,DB: Background Processing
AI-->>API: Moderation Result
API->>DB: Update Verification Status
Luồng Lấy Video cho Mission
%%{init: {'theme':'dark'}}%%
sequenceDiagram
participant Client as 📱 Client
participant API as 🌐 Mission API
participant Storage as 📦 Storage Service
Client->>API: GET /missions/{id}
API->>API: Get Mission Details
alt Mission có Video
API->>Storage: GET /api/v1/files/{fileId}/signed-url
Storage->>Storage: Generate Signed URL (TTL: 1h)
Storage-->>API: { signedUrl, expiresAt }
API-->>Client: Mission { ..., videoUrl }
else Mission không có Video
API-->>Client: Mission { ... }
end
Storage Service Client
public interface IStorageServiceClient
{
/// <summary>
/// Upload file lên Storage Service
/// </summary>
Task<UploadResult> UploadFileAsync(
Stream fileStream,
string fileName,
string contentType,
string folder,
CancellationToken ct);
/// <summary>
/// Lấy signed URL để truy cập file
/// </summary>
Task<SignedUrlResult> GetSignedUrlAsync(
Guid fileId,
TimeSpan ttl,
CancellationToken ct);
/// <summary>
/// Xóa file
/// </summary>
Task DeleteFileAsync(Guid fileId, CancellationToken ct);
}
public record UploadResult(
Guid FileId,
string Url,
string ContentType,
long SizeBytes,
DateTime UploadedAt
);
public record SignedUrlResult(
string SignedUrl,
DateTime ExpiresAt
);
Cấu Hình Storage
public class StorageServiceOptions
{
public string BaseUrl { get; set; } = "http://storage-service:8080";
public int TimeoutSeconds { get; set; } = 30;
public long MaxFileSizeBytes { get; set; } = 50 * 1024 * 1024; // 50MB
public string[] AllowedContentTypes { get; set; } =
{
"image/jpeg", "image/png", "image/webp",
"video/mp4", "video/webm"
};
}
Phụ Thuộc Dịch Vụ
%%{init: {'theme':'dark'}}%%
graph TD
subgraph External["🔐 Xác Thực"]
IAM[IAM Service]
end
subgraph Core["📋 Mission Service"]
Mission[Mission Service]
end
subgraph Integration["🔗 Tích Hợp"]
Wallet[Wallet Service]
Mining[Mining Service]
Social[Social Service]
Storage[Storage Service]
end
IAM -->|Xác thực JWT| Mission
Mission -->|Cấp Điểm| Wallet
Mission <-->|Đồng bộ Referral| Mining
Mission <-->|Dữ liệu Bạn bè| Social
Mission <-->|Upload Media| Storage
style External fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
style Core fill:#8E44AD,color:#ECF0F1,stroke:#7D3C98,stroke-width:3px
style Integration fill:#3498DB,color:#ECF0F1,stroke:#2980B9,stroke-width:2px
Chiến Lược Cache
| Key Pattern | TTL | Mục Đích |
|---|---|---|
mission:{id} |
1h | Cache chi tiết mission |
missions:active |
5m | Danh sách missions hoạt động |
user:{id}:tasks |
10m | Tasks đang làm của user |
user:{id}:checkin |
24h | Trạng thái check-in |
config:missions |
30m | Cấu hình mission |
Bảo Mật
Luồng Chống Gian Lận
%%{init: {'theme':'dark'}}%%
flowchart TD
Request([🚀 Yêu Cầu]) --> RateLimit{⏱️ Rate Limit}
RateLimit -->|Vượt Quá| Block[❌ Chặn]
RateLimit -->|OK| Cooldown{⏳ Cooldown}
Cooldown -->|Quá Nhanh| Wait[⏸️ Chờ]
Cooldown -->|OK| Device{🔍 Kiểm Tra Thiết Bị}
Device -->|Mới/Đáng Ngờ| Flag[⚠️ Đánh Dấu Xem Xét]
Device -->|OK| Evidence{📝 Bằng Chứng Hợp Lệ?}
Evidence -->|Không| Reject[❌ Từ Chối]
Evidence -->|Có| Approve[✅ Duyệt & Thưởng]
style Block fill:#C0392B,color:#ECF0F1,stroke:#A93226,stroke-width:2px
style Reject fill:#E74C3C,color:#ECF0F1,stroke:#C0392B,stroke-width:2px
style Approve fill:#27AE60,color:#ECF0F1,stroke:#229954,stroke-width:2px
Health Checks
| Endpoint | Kiểm Tra |
|---|---|
/health/live |
✅ Service đang chạy |
/health/ready |
✅ DB + Redis + RabbitMQ đã kết nối |
/health |
✅ Trạng thái đầy đủ |
Observability
Metrics
- Số tasks bắt đầu mỗi phút
- Số tasks hoàn thành mỗi phút
- Tỷ lệ check-in
- Số lần nhận thưởng
- Tỷ lệ xác thực thành công
Logging
- Logs JSON có cấu trúc (Serilog)
- Correlation IDs để tracing
- Ẩn dữ liệu nhạy cảm