120 lines
9.6 KiB
Markdown
120 lines
9.6 KiB
Markdown
# Kiến Trúc Hệ Thống: Tính Năng Lập Kế Hoạch Du Lịch (Travel Planning Feature)
|
|
|
|
Tài liệu này mô tả kiến trúc tổng thể, mô hình dữ liệu và các giải pháp kỹ thuật cho tính năng lập kế hoạch du lịch đa người dùng thuộc nền tảng **Travel**.
|
|
|
|
---
|
|
|
|
## 1. Kiến Trúc Tổng Quan (System Overview)
|
|
|
|
Hệ thống được thiết kế theo mô hình **Client-Server** kết hợp với kiến trúc **Modular Monolith** hoặc **Microservices** (tùy quy mô triển khai), chia làm 3 tầng chính:
|
|
|
|
* **Frontend (Presentation Layer):** Đảm nhiệm việc tương tác trực tiếp với người dùng, hiển thị bản đồ trực quan, quản lý luồng tạo tour/chặng và xử lý tải ảnh/hiển thị album.
|
|
* **Backend (Business Logic Layer):** Xử lý xác thực bảo mật, tính toán chi phí (quy tắc phân chia tiền), định tuyến đường đi ngắn nhất, lập lịch kiểm tra task (Cron job/Event-driven) và kiểm tra quyền truy cập (RBAC).
|
|
* **Database & Storage (Data Layer):** Lưu trữ dữ liệu quan hệ (PostgreSQL với extension PostGIS để xử lý tọa độ địa lý) và lưu trữ tệp tin hình ảnh riêng biệt (Object Storage như AWS S3 hoặc MinIO).
|
|
|
|
---
|
|
|
|
## 2. Công Nghệ Đề Xuất (Tech Stack)
|
|
|
|
| Thành phần | Công nghệ đề xuất | Lý do lựa chọn |
|
|
| :--- | :--- | :--- |
|
|
| **Frontend** | React / Next.js + TailwindCSS | Tối ưu cho ứng dụng Single Page (SPA), quản lý trạng thái tốt, hỗ trợ SSR cho SEO nếu cần. |
|
|
| **State Management** | Zustand / Redux Toolkit | Quản lý state phức tạp của Tour, Chặng (Leg) và Chi phí. |
|
|
| **Map Engine** | Mapbox GL JS / Google Maps API / Leaflet | Hỗ trợ hiển thị bản đồ trực quan, marker kéo thả và tính toán định tuyến (Routing). |
|
|
| **Backend** | Node.js (NestJS hoặc Express) | Xử lý bất đồng bộ tốt, phù hợp hệ thống nhiều I/O và tích hợp mDNS/IoT nếu mở rộng sau này. |
|
|
| **Database** | PostgreSQL + **PostGIS** | Hệ quản trị cơ sở dữ liệu mạnh mẽ, PostGIS giúp tính toán khoảng cách và tọa độ địa lý chính xác. |
|
|
| **File Storage** | AWS S3 / MinIO | Lưu trữ ảnh tách biệt hoàn toàn khỏi Database để tối ưu hiệu năng. |
|
|
|
|
---
|
|
|
|
## 3. Mô Hình Dữ Liệu (Database Schema)
|
|
|
|
Dưới đây là các thực thể cốt lõi phục vụ tính năng:
|
|
|
|
### 3.1. Users & Authentication
|
|
* `users`: Lưu thông tin định danh (`id`, `email`, `password_hash`, `name`, `avatar`).
|
|
|
|
### 3.2. Tour & Members (Quản lý đa người dùng & Phân quyền)
|
|
* `tours`: Thông tin tổng quan về chuyến đi.
|
|
* `id` (PK), `title`, `start_date`, `end_date`, `creator_id` (FK), `created_at`.
|
|
* `tour_members`: Bảng trung gian quản lý thành viên và quyền hạn (Bảo mật thông tin).
|
|
* `tour_id` (FK), `user_id` (FK).
|
|
* `role`: Định nghĩa các quyền cụ thể:
|
|
* `OWNER`: Toàn quyền (Người tạo).
|
|
* `EDITOR`: Sửa đổi kế hoạch, chi phí, xem/thêm ảnh.
|
|
* `MEMBER_PLAN_ONLY`: Chỉ xem/sửa kế hoạch, không xem được chi phí.
|
|
* `MEMBER_PHOTO_ONLY`: Chỉ được xem/đăng ảnh trong album, không thấy kế hoạch và chi phí.
|
|
* `VIEWER_EXTERNAL`: Người ngoài được share link, chỉ xem được ảnh công khai (tùy thuộc vào cài đặt privacy).
|
|
|
|
### 3.3. Itinerary & Map (Chặng & Địa điểm)
|
|
* `legs` (Chặng): Một tour có nhiều chặng.
|
|
* `id` (PK), `tour_id` (FK), `sequence_number` (Thứ tự chặng: 1, 2, 3...), `notes`.
|
|
* `places` (Địa điểm trong chặng):
|
|
* `id` (PK), `leg_id` (FK), `name`, `address`, `latitude`, `longitude` (Dữ liệu PostGIS), `sequence_in_leg`.
|
|
* `arrival_time` (Dự kiến), `departure_time` (Dự kiến).
|
|
|
|
### 3.4. Expenses (Chi phí)
|
|
* `expenses`: Lưu vết chi phí cho từng địa điểm/chặng.
|
|
* `id` (PK), `leg_id` (FK), `place_id` (FK, nullable), `category` (`LODGING`, `DINING`, `TRANSPORT`, `OTHER`), `amount` (Số tiền), `currency`, `description`.
|
|
* `tour_cost_summaries`: Cấu hình phân chia tiền ở cuối tour/chặng.
|
|
* `tour_id` (FK), `total_cost`, `adult_count`, `child_count`, `child_discount_percent` (Ví dụ: trẻ em giảm 30%).
|
|
|
|
### 3.5. Tasks & Timeline Tracking (Theo dõi tiến độ)
|
|
* `tasks`: Các hoạt động cần tích chọn hoàn thành.
|
|
* `id` (PK), `tour_id` (FK), `leg_id` (FK), `title`, `planned_timestamp`.
|
|
* `is_completed` (Boolean).
|
|
* `completed_at` (Timestamp - Ghi lại lúc hệ thống tự động tích hoặc user chủ động tích).
|
|
* `trigger_type` (`AUTO_BY_TIME` hoặc `MANUAL_BY_USER`).
|
|
|
|
### 3.6. Album Ảnh (Tách biệt logic kế hoạch)
|
|
* `photos`: Lưu trữ hình ảnh của tour gắn với địa điểm.
|
|
* `id` (PK), `tour_id` (FK), `place_id` (FK, nullable), `uploader_id` (FK), `image_url` (Đường dẫn S3), `captured_at` (Metadata từ ảnh hoặc thời gian tạo), `privacy_level` (`PUBLIC_IN_TOUR`, `PRIVATE_OWNER`).
|
|
|
|
---
|
|
|
|
## 4. Giải Pháp Kiến Trúc Cho Các Tính Năng Core
|
|
|
|
### 4.1. Thuật Toán Tìm Đường Ngắn Nhất (Shortest Path Routing)
|
|
* **Cơ chế:** Khi người dùng thêm các địa điểm vào một chặng, Frontend sẽ gửi danh sách tọa độ hình học (Lat, Lng) về Backend.
|
|
* **Xử lý:** Backend tích hợp với **Mapbox Direction API** hoặc chạy một instance **OSRM (Open Source Routing Machine)** cục bộ để giải bài toán Người đi giao hàng (TSP - Travelling Salesman Problem). Hệ thống sẽ sắp xếp lại thứ tự `sequence_in_leg` của các `places` để tối ưu tổng quãng đường hoặc thời gian di chuyển, sau đó trả về mảng tọa độ đường đi (Polyline) để Frontend vẽ lên bản đồ.
|
|
|
|
### 4.2. Engine Tính Toán Chi Phí & Chia Tiền (Expense Splitting Engine)
|
|
* **Công thức tự động:** * Tổng chi phí Tour ($T$) = $\sum \text{Expenses}$ của tất cả các chặng.
|
|
* Hệ thống cho phép cấu hình số lượng Người lớn ($N_{adult}$) và Trẻ em ($N_{child}$), kèm phần trăm giảm giá của trẻ em ($P_{child\_discount}$).
|
|
* Gọi $X$ là chi phí của 1 người lớn:
|
|
$$T = N_{adult} \times X + N_{child} \times \left( X \times (1 - P_{child\_discount}) \right)$$
|
|
* Từ đó Backend tự động giải phương trình để tìm ra chi phí trung bình của người lớn ($X$) và trẻ em, hiển thị báo cáo chi tiết ở cuối mỗi chặng và kết thúc tour.
|
|
|
|
### 4.3. Theo Dõi Tiến Độ & Tính Toán Độ Lệch Thời Gian (Time Variance Tracking)
|
|
* **Tự động (Auto-trigger):** Một Worker/Cron-job chạy ngầm mỗi phút quét các `tasks` có cài đặt `AUTO_BY_TIME`. Nếu `current_time >= planned_timestamp` và chưa hoàn thành, hệ thống tự động chuyển `is_completed = true` và ghi nhận `completed_at = planned_timestamp`.
|
|
* **Thủ công (Manual-trigger):** Khi user bấm tích chọn hoàn thành, hệ thống ghi nhận ngay `completed_at = current_time`.
|
|
* **Báo cáo thời gian (Time Summary):** Backend tính toán hiệu số:
|
|
$$\Delta t = \text{completed\_at} - \text{planned\_timestamp}$$
|
|
Đồng thời group dữ liệu theo loại hoạt động để xuất ra tổng thời gian: *Di chuyển (dựa trên khoảng cách bản đồ), Tham quan (thời gian ở các địa điểm giải trí), Nghỉ ngơi (thời gian ở chỗ nghỉ).*
|
|
|
|
### 4.4. Bảo Mật & Tách Biệt Album Ảnh (Photo Album Isolation)
|
|
* **Tách biệt logic:** Cơ sở dữ liệu ảnh liên kết qua mã `tour_id` và `place_id` nhưng API endpoint kiểm tra quyền (`/api/v1/tours/:id/photos`) hoạt động độc lập với endpoint kế hoạch (`/api/v1/tours/:id/plans`).
|
|
* **Bảo mật phân quyền (Privacy Middleware):** Trước khi trả về dữ liệu hình ảnh hoặc kế hoạch, hệ thống sẽ chạy qua một tầng Middleware để check quyền trong bảng `tour_members`:
|
|
```javascript
|
|
// Giả lập Logic kiểm tra quyền truy cập thông tin tài chính/kế hoạch
|
|
if (member.role === 'MEMBER_PHOTO_ONLY' && request.path.includes('/plans')) {
|
|
throw new ForbiddenException("Bạn không có quyền xem kế hoạch và chi phí của tour này.");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Thiết Kế Luồng Đi (Workflow Diagrams)
|
|
|
|
### Luồng tạo chặng và tính toán đường đi (Frontend <=> Backend)
|
|
1. **User** tìm kiếm/chọn địa điểm trên bản đồ -> **Frontend** cập nhật state cục bộ.
|
|
2. **User** nhấn "Tối ưu đường đi" -> **Frontend** gửi danh sách `coords[]` lên `/api/v1/routing/optimize`.
|
|
3. **Backend** gọi Mapbox/OSRM API -> Sắp xếp lại thứ tự tối ưu -> Lưu vào DB -> Trả về JSON chứa `optimized_places` và `polyline_geometry`.
|
|
4. **Frontend** render lại danh sách địa điểm theo thứ tự mới và vẽ nét đứt/đường đi trên Map.
|
|
|
|
### Luồng chia sẻ hình ảnh và kiểm tra bảo mật
|
|
1. **User** tải ảnh lên tại một địa điểm trong Tour.
|
|
2. **Frontend** gửi file đến dịch vụ lưu trữ (S3) nhận về `image_url`, sau đó lưu thông tin vào DB kèm metadata (ngày giờ, địa điểm).
|
|
3. Khi một **User khác** truy cập link Tour:
|
|
* Hệ thống check `tour_members`.
|
|
* Nếu thuộc diện *External Viewer* hoặc *Photo Only*: Ẩn hoàn toàn tab "Kế hoạch chặng hành trình" và "Tổng số tiền", chỉ hiển thị giao diện Grid hình ảnh (`album`). |