EVGA
- Phiên bản
- 1.0
- Ngày lập
- 02 / 2026
- Phân loại
- Nội bộ
Chương 01Tổng quan hệ thống
EVGA (European Vietnamese Golf Association) là nền tảng quản lý golf toàn diện, phục vụ hiệp hội golf Việt Nam tại Châu Âu. Hệ thống quản lý giải đấu, trận đấu, điểm chấp (handicap), câu lạc bộ, sân golf và người chơi.
Kiến trúc Clean Architecture với pattern CQRS qua MediatR. Backend ASP.NET Core triển khai hoàn toàn trên Azure.
Các project trong Solution
| Project | Nền tảng | Vai trò | Phụ thuộc |
|---|---|---|---|
EVGA.Web | .NET 7+ | API layer — Controllers, Auth, Middleware, SignalR | ASP.NET Core, Swagger, Hangfire, FluentValidation |
EVGA.Core | .NET 7+ | Business logic — DbContext, Repos, CQRS handlers | EF Core 6+, MediatR, BulkExtensions, Azure.Storage |
EVGA.Models | netstandard2.0 | DTOs, Enums, Validation models dùng chung | System.Text.Json |
EVGA.CommonLogic | netstandard2.0 | Bộ tính Handicap và Score | Chỉ tham chiếu Models |
EVGA.Functions | .NET 7 | Azure Functions — Queue triggers xử lý nền | Azure WebJobs, Firebase |
Tests | .NET 7 | Unit + Integration tests | NUnit, FluentAssertions, Moq |
Pattern CQRS
Toàn bộ business logic nằm trong Command/Query handlers. Controllers chỉ dispatch qua _mediator.Send().
| Loại | Quy ước | Ví dụ | Trả về |
|---|---|---|---|
| Query | *Query / *QueryHandler | GetGameDetailQuery | Dữ liệu (chỉ đọc) |
| Command | *Command / *CommandHandler | CreateGameCommand | Result |
Chương 02Hạ tầng Azure
Tổng cộng 51 resources trong subscription sub-evga, vùng chính West Europe. 3 môi trường độc lập.
Ba môi trường
| Env | RG | Backend | Admin | DB | Functions | Storage |
|---|---|---|---|---|---|---|
| DEV | EVGA-dev | evga-dev | evga-admin-static-dev | evga-dev | evga-dev-functions | evgadevstorage |
| UAT | EVGA-uat | evga-uat | evga-admin-static-uat | evga-uat | evga-uat-functions | evgauatstorage |
| PROD | EVGA | evga | evga-admin-static | evga-prod | evga-functions | evgasblobtorage |
Thành phần
Chương 03Sơ đồ kiến trúc
Dựa trên sơ đồ gốc từ developer Jakub bàn giao, kết hợp phân tích source code.
Luồng request chính
Luồng xử lý nền
SQL Server topology
Chương 04Cơ sở dữ liệu
EF Core 6+ Code-First migrations. DateTime tự động UTC qua UtcDateAnnotation.
Bảng chính
| Entity | Trường chính | Quan hệ | Ghi chú |
|---|---|---|---|
| User | Id (gán sẵn), Name, HandicapIndex, Gender, PasswordHash, FirebaseToken, AdminOf | Clubs, Friends, Stats, InitialScores | Id không auto-increment |
| Game | Id, TimeLocal, GameStatus, Type, CourseId, TournamentId? | PlayersScore[], Course | Draft → Saved → Sent → Confirmed |
| PlayerScore | Id, UserId, HitsJson (JSON), tất cả loại điểm | Game, User | Gross, Net, Stb Netto/Brutto, ScoreDiff |
| Course | Id, Name, HolesCount, GolfClubId, TimeZone | Tees[], Holes[] | TimeZone cho giờ local |
| Tee | Id, TeeColor, Gender, SlopeRating, CourseRating | Course | Color + Gender duy nhất/sân |
| Tournament | Id, Name, Status, StartTime, nhiều config | Flights[], Players[], Categories[] | Draft → Published → Started → Finished |
| TournamentFlight | Id, TimeLocal, Hole, MaxPlayers | Players[], Course | Nhóm tee time |
| TournamentPlayer | Id, UserId, FlightId?, TeeColor | User, Flight?, Score? | Liên kết player ↔ tournament |
| Club | Id, Name, Country, CountryParentId? | Parent hierarchy | EVGA → Quốc gia → CLB |
| Message | Id, ForUserId, Content, GameId? | ForUser | Thông báo in-app |
| YearlyStatistics | UserId + Year (composite key) | Best games, courses | Tính lại qua Functions |
Migration commands
- Tạo:
dotnet ef migrations add {Tên} --startup-project EVGA.Web --project EVGA.Core - Chạy:
dotnet ef database update --startup-project EVGA.Web --project EVGA.Core - Xuất SQL:
dotnet ef migrations script {from} --startup-project EVGA.Web --project EVGA.Core
database update trực tiếp.Chương 05API Endpoints
JWT Bearer required (trừ login). Swagger tại /swagger.
/api/users
| Method | Endpoint | Mô tả |
|---|---|---|
| POST | /api/users/login | Đăng nhập → JWT token |
| GET | /api/users/profile | Profile bản thân |
| GET | /api/users/handicap | Chi tiết handicap + score differentials |
| GET | /api/users/statistics/{year?} | Thống kê năm |
| POST | /api/users/firebase/token | Đăng ký push token |
| PUT | /api/users/logout | Đăng xuất |
/api/games
| Method | Endpoint | Mô tả |
|---|---|---|
| POST | /api/games/data | Tính dữ liệu game trước khi tạo |
| POST | /api/games/ | Tạo + xác nhận game, tính lại handicap |
| PUT | /api/games/{id}/score | Sửa điểm |
| GET | /api/games/{id} | Chi tiết game |
/api/tournaments
| Method | Endpoint | Mô tả |
|---|---|---|
| GET | /api/tournaments | Danh sách giải đấu |
| POST | /api/tournaments/{id}/signup | Đăng ký tham gia |
| POST | /api/tournaments/{id}/publish-startlist | Công bố start list (admin) |
| POST | /api/tournaments/{id}/start | Bắt đầu giải (admin) |
| POST | /api/tournaments/{id}/end | Kết thúc giải (admin) |
| POST | /api/tournaments/{id}/generate-games | Tạo game từ kết quả |
Chương 06Logic nghiệp vụ
Hệ thống Handicap
- Score Differential = (AGS − Course Rating) × 113 / Slope Rating
- Game Handicap = Player HCP × (Slope / 113) + (Course Rating − Par)
- Player HCP = Trung bình N tốt nhất trong 20 game + hệ số
- Chưa đủ 20 game → dùng InitialGameScore
- Tính lại sau mỗi game qua update-stats-queue
Code: IHandicapCalculator (CommonLogic), HandicapService (Core)
Các loại điểm
| Loại | Công thức | Mục đích |
|---|---|---|
| GrossScore | Tổng hits | Tổng gốc |
| NetScore | Gross − GameHandicap | Sau điều chỉnh HCP |
| AdjustedGross | Hits cap par+2 mỗi hố | Đầu vào tính HCP |
| StbNetto | Stableford netto | Xếp hạng giải (netto) |
| StbBrutto | Stableford brutto | Xếp hạng giải (brutto) |
| ScoreDifferential | (AGS − CR) × 113 / SR | Tính Handicap Index |
Vòng đời giải đấu
- Draft — Admin cấu hình categories, tees, flights
- Đăng ký — Users signup, admin có thể thêm
- Phân flight — Gán vào tee time
- Publish start list — Khóa danh sách
- Start — Nhập điểm
- End — Kiểm tra điểm, đẩy queue
- Generate games — Functions tạo Game records, tính lại HCP
Luồng tạo game — CreateGameCommandHandler
- Validate người chơi, tee, điểm đầy đủ, thứ tự ngày
- Tạo PlayerScore qua
PlayerScoreFactory - Tính tất cả loại điểm + handicap mới
- Cập nhật LastGameId, LastGameDate
- Lưu DB trong transaction
- Đẩy
UpdateStatsMessagevào queue - Gửi push notification + tin nhắn in-app
Chương 07Xác thực & Phân quyền
Login bằng userId (số) + password → JWT token chứa UserId, Name, Email, HomeClubId, AdminOf.
| Tham số | Giá trị |
|---|---|
| Issuer | EvgaApi |
| Audience | EvgaMobileApp |
| System User | userId = 1, dùng cho Functions gọi API |
Chính sách phân quyền
| Policy | Mô tả | Dùng cho |
|---|---|---|
| AdminOnly | AdminOf chứa clubId liên quan | Quản lý CLB/user |
| ParentAdminOnly | Admin CLB cha | Xuyên CLB |
| EvgaAdminOnly | Admin EVGA top-level | Toàn hệ thống |
| SystemOnly | userId=1 | API nội bộ từ Functions |
Chương 08Xử lý nền & Thông báo
| Function | Queue | Hành động |
|---|---|---|
| update-statistics | update-stats-queue | Recalculate personal + course stats per user |
| generate-tournament-games | tournament-games-queue | Tạo Game records từ tournament |
Functions đăng nhập system user → JWT → gọi API. Firebase FCM gửi push qua topics (all-dev, all-uat, all-prod). Lỗi notification không crash (catch + log warning).
Hangfire: SQL storage, dashboard /hangfire. SignalR: Hub tại /pubsub, live scores.
Chương 09Cấu hình
| Section | Keys | Ghi chú |
|---|---|---|
| ConnectionStrings | EvgaContext | PROD: Azure App Settings (không trong file) |
| AuthenticationConfig | Secret, SystemHash, SystemUser | DEV/UAT chung Secret, PROD Hash khác |
| BlobStorageConfig | ConnectionString, ContainerName | profile-images container |
| QueueClientConfig | StatsQueue, NotificationsQueue, TournamentGamesQueue | Cùng storage account với Blob |
| Firebase | Key (JSON), AllTopicName | FCM config |
Chương 10CI/CD & Triển khai
| Pipeline | Config | Đặc biệt |
|---|---|---|
azure-pipelines-dev.yml | Development | Standard publish |
azure-pipelines-uat.yml | UAT | Standard publish |
azure-pipelines-prod.yml | PROD | Self-contained, win-x86 |
Steps: Restore → Build → Test → Publish → Artifacts. Auth bằng Managed Identity.
Docker
- Build:
docker build --force-rm -f EVGA.Web/Dockerfile -t evgaweb . - Run:
docker run -it --rm -p 5010:80 -e ASPNETCORE_ENVIRONMENT=Development evga-web:1.0.0
Chương 11Hướng dẫn phát triển
Thêm tính năng
- Model — DTO trong
EVGA.Models/{Feature}/ - Query/Command —
EVGA.Core/{Feature}/, implementIRequest - Handler — implement
IRequestHandler, dùngEvgaContext - Controller — endpoint trong
EVGA.Web/API/, gọi_mediator.Send() - Test — integration test trong
Tests/EVGA.Web.IntegrationTests/
Vấn đề thường gặp
Kết thúc giải đấu chậm
Handler load toàn bộ flights/players/scores vào RAM → tạo Game tuần tự. Fix: batch + BulkInsert.
N+1 queries khi thêm players vào flight
AddPlayerToFlightCommandHandler Include() mỗi lần. Fix: batch assignment hoặc stored procedure.
Handicap > 54
Thêm Math.Min(result, 54m) trong HandicapCalculator.CalculatePlayerHandicap().
Chương 12Vấn đề bảo mật
| Vấn đề | Chi tiết | Giải pháp |
|---|---|---|
| SQL password trong code | appsettings.Development/UAT.json chứa password rõ ràng | Azure Key Vault hoặc Managed Identity |
| Firebase key trong repo | firebase-config.json chứa private key | Rotate key, Key Vault, xóa git history |
| Storage account key | AccountKey trong appsettings | Managed Identity |
| JWT Secret chung | DEV/UAT dùng cùng Secret | Secret riêng mỗi env |
| SQL user chung | jakubjenis@evga cho DEV + UAT | User riêng, quyền tối thiểu |
| Không rate limit | /api/users/login không giới hạn | Rate limit qua APIM/middleware |
Chương 13TODO & Tối ưu
Ưu tiên cao
- Giải đấu EVGA luôn hiển thị đầu tiên
- Chỉ start tournament vào đúng ngày
- HCP tối đa 54
- GPS cho holes
Ưu tiên trung bình
- Sửa reserved IDs
- Export tournament → Excel
Tối ưu hiệu suất
- Cache course queries
- Cache user detail
- Tối ưu flight assignment + tournament finish cho giải lớn
Chương 14Bàn giao hệ thống
Checklist các thông tin cần nhận bàn giao từ developer gốc (Jakub).
14.1 Tài khoản & Quyền truy cập
- ✓Azure Portal — Subscription sub-evgaOwner/Contributor trên subscription. 3 resource groups: EVGA, EVGA-uat, EVGA-dev
- ✓Azure SQL Server — Tài khoản adminThay thế jakubjenis@evga. Password hiện tại cần ĐỔI NGAY
- ✓Azure DevOps — Pipeline accessAdmin project DevOps. Service Connections & Managed Identities
- ✓Firebase Console — evga-f8306Owner trên Firebase project. Cloud Messaging, rotate key
- ✓Source code repositoryAdmin trên Git repo. Branch strategy, protection rules
14.2 Credentials & Secrets
- ✓JWT SecretRotate và chuyển vào Key Vault
- ✓System User credentialsuserId=1, SystemHash. Password cho Functions config
- ✓Storage Account Keys3 accounts — nên chuyển sang Managed Identity
- ✓Firebase Service Account KeyCẦN ROTATE — đã commit vào repo
- ✓APIM Subscription KeysKeys cho API Management nếu client apps cần
- ✓Azure App Settings PRODConnection string PROD nằm trong Azure — xác nhận đầy đủ
14.3 Tài liệu & Source code
- ✓Backend source + Git historyRepo, branch chính, release strategy
- ✓Mobile app source codeRepo riêng iOS/Android? Framework?
- ✓Admin web source codeReact repo, build & deploy process
- ✓Database backup mới nhấtBackup hiện có: 2024-07-01. Cần bản mới
- ✓Deploy processCI/CD: commit → pipeline → auto hay manual?
- ✓Monitoring setupAi nhận alerts? Cần update Action Group
- ✓Admin users listAi có quyền gì trong hệ thống
14.4 Hành động SAU bàn giao
- Đổi SQL password — thay password cho tất cả environments
- Rotate Firebase key — tạo key mới, cập nhật Azure App Settings
- Rotate JWT Secret — users phải đăng nhập lại
- Chuyển secrets → Azure Key Vault
- Tạo SQL users riêng mỗi env, quyền tối thiểu
- Thu hồi quyền dev cũ — Azure, DevOps, Firebase, repo
- Cập nhật alert recipients
- Full backup PROD + test restore trên failover
Chương 15Tra cứu nhanh
Chạy local
dotnet run --project EVGA.Web→ https://localhost:5001- Swagger:
/swagger· Hangfire:/hangfire· SignalR:/pubsub - Env:
ASPNETCORE_ENVIRONMENT=Development
File quan trọng
| Gì | Ở đâu |
|---|---|
| Solution | src/EVGA.sln |
| Startup | src/EVGA.Web/Startup.cs |
| DI | src/EVGA.Web/DI/ContainerExtensions.cs |
| DbContext | src/EVGA.Core/Database/EvgaContext.cs |
| Migrations | src/EVGA.Core/Migrations/ |
| Controllers | src/EVGA.Web/API/ |
| CQRS Handlers | src/EVGA.Core/{Feature}/Commands/ & Queries/ |
| Calculators | src/EVGA.CommonLogic/Calculators/ |
| Functions | src/EVGA.Functions/ |
| Pipelines | azure-pipelines-{env}.yml |
| Config | src/EVGA.Web/appsettings.{Env}.json |
| Middleware | src/EVGA.Web/Middlewares/ApiExceptionHandlingMiddleware.cs |
| SignalR Hub | src/EVGA.Web/Hubs/PubSubHub.cs |
Quoc Toan Tran
EVGA
- Phiên bản
- 1.0
- Ngày lập
- 02 / 2026
- Phân loại
- Nội bộ
Chương 01Tổng quan hệ thống
EVGA (European Vietnamese Golf Association) là nền tảng quản lý golf toàn diện, phục vụ hiệp hội golf Việt Nam tại Châu Âu. Hệ thống quản lý giải đấu, trận đấu, điểm chấp (handicap), câu lạc bộ, sân golf và người chơi.
Kiến trúc Clean Architecture với pattern CQRS qua MediatR. Backend ASP.NET Core triển khai hoàn toàn trên Azure.
Các project trong Solution
| Project | Nền tảng | Vai trò | Phụ thuộc |
|---|---|---|---|
EVGA.Web | .NET 7+ | API layer — Controllers, Auth, Middleware, SignalR | ASP.NET Core, Swagger, Hangfire, FluentValidation |
EVGA.Core | .NET 7+ | Business logic — DbContext, Repos, CQRS handlers | EF Core 6+, MediatR, BulkExtensions, Azure.Storage |
EVGA.Models | netstandard2.0 | DTOs, Enums, Validation models dùng chung | System.Text.Json |
EVGA.CommonLogic | netstandard2.0 | Bộ tính Handicap và Score | Chỉ tham chiếu Models |
EVGA.Functions | .NET 7 | Azure Functions — Queue triggers xử lý nền | Azure WebJobs, Firebase |
Tests | .NET 7 | Unit + Integration tests | NUnit, FluentAssertions, Moq |
Pattern CQRS
Toàn bộ business logic nằm trong Command/Query handlers. Controllers chỉ dispatch qua _mediator.Send().
| Loại | Quy ước | Ví dụ | Trả về |
|---|---|---|---|
| Query | *Query / *QueryHandler | GetGameDetailQuery | Dữ liệu (chỉ đọc) |
| Command | *Command / *CommandHandler | CreateGameCommand | Result<T> hoặc Unit |
Chương 02Hạ tầng Azure
Tổng cộng 51 resources trong subscription sub-evga, vùng chính West Europe. 3 môi trường độc lập.
Ba môi trường
| Env | RG | Backend | Admin | DB | Functions | Storage |
|---|---|---|---|---|---|---|
| DEV | EVGA-dev | evga-dev | evga-admin-static-dev | evga-dev | evga-dev-functions | evgadevstorage |
| UAT | EVGA-uat | evga-uat | evga-admin-static-uat | evga-uat | evga-uat-functions | evgauatstorage |
| PROD | EVGA | evga | evga-admin-static | evga-prod | evga-functions | evgasblobtorage |
Thành phần
Chương 03Sơ đồ kiến trúc
Dựa trên sơ đồ gốc từ developer Jakub bàn giao, kết hợp phân tích source code.
Luồng request chính
Luồng xử lý nền
SQL Server topology
Chương 04Cơ sở dữ liệu
EF Core 6+ Code-First migrations. DateTime tự động UTC qua UtcDateAnnotation.
Bảng chính
| Entity | Trường chính | Quan hệ | Ghi chú |
|---|---|---|---|
| User | Id (gán sẵn), Name, HandicapIndex, Gender, PasswordHash, FirebaseToken, AdminOf | Clubs, Friends, Stats, InitialScores | Id không auto-increment |
| Game | Id, TimeLocal, GameStatus, Type, CourseId, TournamentId? | PlayersScore[], Course | Draft → Saved → Sent → Confirmed |
| PlayerScore | Id, UserId, HitsJson (JSON), tất cả loại điểm | Game, User | Gross, Net, Stb Netto/Brutto, ScoreDiff |
| Course | Id, Name, HolesCount, GolfClubId, TimeZone | Tees[], Holes[] | TimeZone cho giờ local |
| Tee | Id, TeeColor, Gender, SlopeRating, CourseRating | Course | Color + Gender duy nhất/sân |
| Tournament | Id, Name, Status, StartTime, nhiều config | Flights[], Players[], Categories[] | Draft → Published → Started → Finished |
| TournamentFlight | Id, TimeLocal, Hole, MaxPlayers | Players[], Course | Nhóm tee time |
| TournamentPlayer | Id, UserId, FlightId?, TeeColor | User, Flight?, Score? | Liên kết player ↔ tournament |
| Club | Id, Name, Country, CountryParentId? | Parent hierarchy | EVGA → Quốc gia → CLB |
| Message | Id, ForUserId, Content, GameId? | ForUser | Thông báo in-app |
| YearlyStatistics | UserId + Year (composite key) | Best games, courses | Tính lại qua Functions |
Migration commands
- Tạo:
dotnet ef migrations add {Tên} --startup-project EVGA.Web --project EVGA.Core - Chạy:
dotnet ef database update --startup-project EVGA.Web --project EVGA.Core - Xuất SQL:
dotnet ef migrations script {from} --startup-project EVGA.Web --project EVGA.Core
database update trực tiếp.Chương 05API Endpoints
JWT Bearer required (trừ login). Swagger tại /swagger.
/api/users
| Method | Endpoint | Mô tả |
|---|---|---|
| POST | /api/users/login | Đăng nhập → JWT token |
| GET | /api/users/profile | Profile bản thân |
| GET | /api/users/handicap | Chi tiết handicap + score differentials |
| GET | /api/users/statistics/{year?} | Thống kê năm |
| POST | /api/users/firebase/token | Đăng ký push token |
| PUT | /api/users/logout | Đăng xuất |
/api/games
| Method | Endpoint | Mô tả |
|---|---|---|
| POST | /api/games/data | Tính dữ liệu game trước khi tạo |
| POST | /api/games/ | Tạo + xác nhận game, tính lại handicap |
| PUT | /api/games/{id}/score | Sửa điểm |
| GET | /api/games/{id} | Chi tiết game |
/api/tournaments
| Method | Endpoint | Mô tả |
|---|---|---|
| GET | /api/tournaments | Danh sách giải đấu |
| POST | /api/tournaments/{id}/signup | Đăng ký tham gia |
| POST | /api/tournaments/{id}/publish-startlist | Công bố start list (admin) |
| POST | /api/tournaments/{id}/start | Bắt đầu giải (admin) |
| POST | /api/tournaments/{id}/end | Kết thúc giải (admin) |
| POST | /api/tournaments/{id}/generate-games | Tạo game từ kết quả |
Chương 06Logic nghiệp vụ
Hệ thống Handicap
- Score Differential = (AGS − Course Rating) × 113 / Slope Rating
- Game Handicap = Player HCP × (Slope / 113) + (Course Rating − Par)
- Player HCP = Trung bình N tốt nhất trong 20 game + hệ số
- Chưa đủ 20 game → dùng InitialGameScore
- Tính lại sau mỗi game qua update-stats-queue
Code: IHandicapCalculator (CommonLogic), HandicapService (Core)
Các loại điểm
| Loại | Công thức | Mục đích |
|---|---|---|
| GrossScore | Tổng hits | Tổng gốc |
| NetScore | Gross − GameHandicap | Sau điều chỉnh HCP |
| AdjustedGross | Hits cap par+2 mỗi hố | Đầu vào tính HCP |
| StbNetto | Stableford netto | Xếp hạng giải (netto) |
| StbBrutto | Stableford brutto | Xếp hạng giải (brutto) |
| ScoreDifferential | (AGS − CR) × 113 / SR | Tính Handicap Index |
Vòng đời giải đấu
- Draft — Admin cấu hình categories, tees, flights
- Đăng ký — Users signup, admin có thể thêm
- Phân flight — Gán vào tee time
- Publish start list — Khóa danh sách
- Start — Nhập điểm
- End — Kiểm tra điểm, đẩy queue
- Generate games — Functions tạo Game records, tính lại HCP
Luồng tạo game — CreateGameCommandHandler
- Validate người chơi, tee, điểm đầy đủ, thứ tự ngày
- Tạo PlayerScore qua
PlayerScoreFactory - Tính tất cả loại điểm + handicap mới
- Cập nhật LastGameId, LastGameDate
- Lưu DB trong transaction
- Đẩy
UpdateStatsMessagevào queue - Gửi push notification + tin nhắn in-app
Chương 07Xác thực & Phân quyền
Login bằng userId (số) + password → JWT token chứa UserId, Name, Email, HomeClubId, AdminOf.
| Tham số | Giá trị |
|---|---|
| Issuer | EvgaApi |
| Audience | EvgaMobileApp |
| System User | userId = 1, dùng cho Functions gọi API |
Chính sách phân quyền
| Policy | Mô tả | Dùng cho |
|---|---|---|
| AdminOnly | AdminOf chứa clubId liên quan | Quản lý CLB/user |
| ParentAdminOnly | Admin CLB cha | Xuyên CLB |
| EvgaAdminOnly | Admin EVGA top-level | Toàn hệ thống |
| SystemOnly | userId=1 | API nội bộ từ Functions |
Chương 08Xử lý nền & Thông báo
| Function | Queue | Hành động |
|---|---|---|
| update-statistics | update-stats-queue | Recalculate personal + course stats per user |
| generate-tournament-games | tournament-games-queue | Tạo Game records từ tournament |
Functions đăng nhập system user → JWT → gọi API. Firebase FCM gửi push qua topics (all-dev, all-uat, all-prod). Lỗi notification không crash (catch + log warning).
Hangfire: SQL storage, dashboard /hangfire. SignalR: Hub tại /pubsub, live scores.
Chương 09Cấu hình
| Section | Keys | Ghi chú |
|---|---|---|
| ConnectionStrings | EvgaContext | PROD: Azure App Settings (không trong file) |
| AuthenticationConfig | Secret, SystemHash, SystemUser | DEV/UAT chung Secret, PROD Hash khác |
| BlobStorageConfig | ConnectionString, ContainerName | profile-images container |
| QueueClientConfig | StatsQueue, NotificationsQueue, TournamentGamesQueue | Cùng storage account với Blob |
| Firebase | Key (JSON), AllTopicName | FCM config |
Chương 10CI/CD & Triển khai
| Pipeline | Config | Đặc biệt |
|---|---|---|
azure-pipelines-dev.yml | Development | Standard publish |
azure-pipelines-uat.yml | UAT | Standard publish |
azure-pipelines-prod.yml | PROD | Self-contained, win-x86 |
Steps: Restore → Build → Test → Publish → Artifacts. Auth bằng Managed Identity.
Docker
- Build:
docker build --force-rm -f EVGA.Web/Dockerfile -t evgaweb . - Run:
docker run -it --rm -p 5010:80 -e ASPNETCORE_ENVIRONMENT=Development evga-web:1.0.0
Chương 11Hướng dẫn phát triển
Thêm tính năng
- Model — DTO trong
EVGA.Models/{Feature}/ - Query/Command —
EVGA.Core/{Feature}/, implementIRequest<T> - Handler — implement
IRequestHandler, dùngEvgaContext - Controller — endpoint trong
EVGA.Web/API/, gọi_mediator.Send() - Test — integration test trong
Tests/EVGA.Web.IntegrationTests/
Vấn đề thường gặp
Kết thúc giải đấu chậm
Handler load toàn bộ flights/players/scores vào RAM → tạo Game tuần tự. Fix: batch + BulkInsert.
N+1 queries khi thêm players vào flight
AddPlayerToFlightCommandHandler Include() mỗi lần. Fix: batch assignment hoặc stored procedure.
Handicap > 54
Thêm Math.Min(result, 54m) trong HandicapCalculator.CalculatePlayerHandicap().
Chương 12Vấn đề bảo mật
| Vấn đề | Chi tiết | Giải pháp |
|---|---|---|
| SQL password trong code | appsettings.Development/UAT.json chứa password rõ ràng | Azure Key Vault hoặc Managed Identity |
| Firebase key trong repo | firebase-config.json chứa private key | Rotate key, Key Vault, xóa git history |
| Storage account key | AccountKey trong appsettings | Managed Identity |
| JWT Secret chung | DEV/UAT dùng cùng Secret | Secret riêng mỗi env |
| SQL user chung | jakubjenis@evga cho DEV + UAT | User riêng, quyền tối thiểu |
| Không rate limit | /api/users/login không giới hạn | Rate limit qua APIM/middleware |
Chương 13TODO & Tối ưu
Ưu tiên cao
- Giải đấu EVGA luôn hiển thị đầu tiên
- Chỉ start tournament vào đúng ngày
- HCP tối đa 54
- GPS cho holes
Ưu tiên trung bình
- Sửa reserved IDs
- Export tournament → Excel
Tối ưu hiệu suất
- Cache course queries
- Cache user detail
- Tối ưu flight assignment + tournament finish cho giải lớn
Chương 14Bàn giao hệ thống
Checklist các thông tin cần nhận bàn giao từ developer gốc (Jakub).
14.1 Tài khoản & Quyền truy cập
- ✓Azure Portal — Subscription sub-evgaOwner/Contributor trên subscription. 3 resource groups: EVGA, EVGA-uat, EVGA-dev
- ✓Azure SQL Server — Tài khoản adminThay thế jakubjenis@evga. Password hiện tại cần ĐỔI NGAY
- ✓Azure DevOps — Pipeline accessAdmin project DevOps. Service Connections & Managed Identities
- ✓Firebase Console — evga-f8306Owner trên Firebase project. Cloud Messaging, rotate key
- ✓Source code repositoryAdmin trên Git repo. Branch strategy, protection rules
14.2 Credentials & Secrets
- ✓JWT SecretRotate và chuyển vào Key Vault
- ✓System User credentialsuserId=1, SystemHash. Password cho Functions config
- ✓Storage Account Keys3 accounts — nên chuyển sang Managed Identity
- ✓Firebase Service Account KeyCẦN ROTATE — đã commit vào repo
- ✓APIM Subscription KeysKeys cho API Management nếu client apps cần
- ✓Azure App Settings PRODConnection string PROD nằm trong Azure — xác nhận đầy đủ
14.3 Tài liệu & Source code
- ✓Backend source + Git historyRepo, branch chính, release strategy
- ✓Mobile app source codeRepo riêng iOS/Android? Framework?
- ✓Admin web source codeReact repo, build & deploy process
- ✓Database backup mới nhấtBackup hiện có: 2024-07-01. Cần bản mới
- ✓Deploy processCI/CD: commit → pipeline → auto hay manual?
- ✓Monitoring setupAi nhận alerts? Cần update Action Group
- ✓Admin users listAi có quyền gì trong hệ thống
14.4 Hành động SAU bàn giao
- Đổi SQL password — thay password cho tất cả environments
- Rotate Firebase key — tạo key mới, cập nhật Azure App Settings
- Rotate JWT Secret — users phải đăng nhập lại
- Chuyển secrets → Azure Key Vault
- Tạo SQL users riêng mỗi env, quyền tối thiểu
- Thu hồi quyền dev cũ — Azure, DevOps, Firebase, repo
- Cập nhật alert recipients
- Full backup PROD + test restore trên failover
Chương 15Tra cứu nhanh
Chạy local
dotnet run --project EVGA.Web→ https://localhost:5001- Swagger:
/swagger· Hangfire:/hangfire· SignalR:/pubsub - Env:
ASPNETCORE_ENVIRONMENT=Development
File quan trọng
| Gì | Ở đâu |
|---|---|
| Solution | src/EVGA.sln |
| Startup | src/EVGA.Web/Startup.cs |
| DI | src/EVGA.Web/DI/ContainerExtensions.cs |
| DbContext | src/EVGA.Core/Database/EvgaContext.cs |
| Migrations | src/EVGA.Core/Migrations/ |
| Controllers | src/EVGA.Web/API/ |
| CQRS Handlers | src/EVGA.Core/{Feature}/Commands/ & Queries/ |
| Calculators | src/EVGA.CommonLogic/Calculators/ |
| Functions | src/EVGA.Functions/ |
| Pipelines | azure-pipelines-{env}.yml |
| Config | src/EVGA.Web/appsettings.{Env}.json |
| Middleware | src/EVGA.Web/Middlewares/ApiExceptionHandlingMiddleware.cs |
| SignalR Hub | src/EVGA.Web/Hubs/PubSubHub.cs |