Ứng dụng theo dõi thể chất thông minh: Cấu trúc dữ liệu, giản đồ cơ sở dữ liệu và thông số kỹ thuật thiết kế máy chủ
Tạo tácĐây là cấu phần phần mềm được dùng trong Tập 2: Lập kế hoạch cho thông số kỹ thuật hệ thống của ứng dụng (Không cần mã). Bạn có thể sử dụng quy cách này làm tài liệu tham khảo để định cấu trúc cho các tập hợp Cloud Firestore, điểm cuối máy chủ và bộ nhớ tác nhân 3 tầng.
Đăng nhập để truy cập vào dữ liệu này
Đăng nhập bằng Hồ sơ nhà phát triển của Google để xem và sao chép toàn bộ cấu phần phần mềm được dùng trong loạt bài Build It! (Xây dựng).
Bắt buộc phải có Hồ sơ nhà phát triển
Bạn không đủ điều kiện để tạo hồ sơ nhà phát triển. Hãy thông báo cho quản trị viên Workspace của bạn để bật tính năng này.
Tạo hồ sơ nhà phát triển
Tạo Hồ sơ nhà phát triển của Google miễn phí để sử dụng tất cả câu lệnh và cấu phần phần mềm của Build It!
Tham gia Builder Central để mở khoá
Chỉ cần một cú nhấp chuột là bạn có thể nhận huy hiệu hội viên Builder miễn phí để mở khoá tất cả câu lệnh và dữ liệu của Build It!
Trạng thái: Đang thực hiện (Đang soạn thảo bằng Trợ lý lập kế hoạch)
Cơ sở dữ liệu mục tiêu: Google Cloud Firestore
Mục tiêu kiến trúc: Bản dựng lại sạch, dễ bảo trì, sẵn sàng cho môi trường thực tế của nguyên mẫu thể dục thông minh AI Studio.
0. Các quyết định về cấu trúc hệ thống và bộ công nghệ cốt lõi
Để đảm bảo độ ổn định, hiệu suất và ranh giới rõ ràng trong quá trình sản xuất, cơ sở kiến trúc sau đây được khoá cho tất cả các hoạt động triển khai:
- Khung giao diện người dùng: Next.js (App Router, React 19) có tính năng lưu tài sản ngoại tuyến vào bộ nhớ đệm của Ứng dụng web tiến bộ (PWA) (sử dụng Serwist/
@ducanh2912/next-pwa). - Thành phần giao diện người dùng và kiểu: Tailwind CSS phiên bản 4 + Biến thể Tailwind (để quản lý biến thể) + Lucide React (biểu tượng).
- Kiến trúc trạng thái ứng dụng:
- Zustand (với phần mềm trung gian
persist): Xử lý trạng thái cục bộ, xúc giác và tạm thời của ứng dụng (đồng hồ bấm giờ, vùng đệm tập luyện đang hoạt động, biểu đồ bước và dữ liệu đầu vào chưa lưu trong phòng tập). - TanStack Query (React Query): Xử lý tất cả trạng thái máy chủ không đồng bộ, lưu tài liệu Firestore vào bộ nhớ đệm, xác thực lại trong nền, loại bỏ trùng lặp yêu cầu và đột biến lạc quan.
- Zustand (với phần mềm trung gian
- Môi trường thời gian chạy phụ trợ và dịch vụ lưu trữ API: Trình xử lý định tuyến Next.js (
app/api/...) được lưu trữ trên Google Cloud Run trong cùng khu vực với Firestore và Bộ nhớ đệm ngữ cảnh Gemini để có độ trễ nội bộ dưới 15 mili giây. - Xác thực: Xác thực Firebase (Đăng nhập bằng Google, Email/Mật khẩu và liên kết tham gia ẩn danh). Ứng dụng khách đính kèm
Authorization: Bearer <Firebase_ID_Token>vào tất cả yêu cầu API; máy chủ xác minh danh tính bằngfirebase-admin.auth().verifyIdToken(token). - Cơ sở dữ liệu chính: Google Cloud Firestore.
1. Tổng quan về hệ thống và hành trình của người dùng
1.1 Phạm vi và nguyên tắc về cấu trúc
Thông số kỹ thuật này xác định các mô hình dữ liệu, giản đồ Cloud Firestore, hợp đồng điểm cuối máy chủ và cấu trúc bộ nhớ của tác nhân AI cho ứng dụng Smart Fitness. Lớp này thiết lập một nguồn đáng tin cậy duy nhất cho tất cả hoạt động lập kế hoạch tập luyện, ghi nhật ký phiên trước đây và bộ nhớ huấn luyện theo cấp độ tự động, tách biệt hoàn toàn với các khung trình bày giao diện người dùng (được đề cập trong thông số kỹ thuật thiết kế giao diện người dùng/trải nghiệm người dùng chuyên biệt).
1.2 Nguyên tắc cốt lõi về dữ liệu
- Phân tách mối quan ngại: Ranh giới nghiêm ngặt giữa hợp đồng dữ liệu (phần phụ trợ/cơ sở dữ liệu) và thiết kế giao diện người dùng của ứng dụng.
- Kiến trúc thư viện kết hợp: Bách khoa toàn thư về bài tập tĩnh, không thể thay đổi (
exercises) kết hợp với thành phần AI linh động của lịch biểu hằng tuần (weekly_plans). - Đột biến theo giai đoạn có sự can thiệp của con người: Các thay đổi về bài tập do AI tạo ra được trả về dưới dạng đề xuất theo giai đoạn; việc ghi vào cơ sở dữ liệu chỉ xảy ra khi người dùng chấp nhận rõ ràng.
- Bộ nhớ sống 3 tầng liên tục: AI tự động thu thập, cập nhật và xoá thông tin thực tế về người dùng trên các yếu tố cố định, trạng thái sinh lý hết hạn (với TTL rõ ràng) và bối cảnh tình huống tạm thời.
- Đọc từng tài liệu một cách chi tiết: Dữ liệu được cấu trúc thành các tài liệu tối ưu (chẳng hạn như sổ tay xoắn ốc kế hoạch hằng tuần và nhóm trò chuyện hằng tuần) để giảm thiểu độ trễ đọc và loại bỏ các truy vấn thừa.
2. Điểm cuối của máy chủ và hợp đồng API
Mỗi điểm cuối phải chỉ định Nguồn phản hồi (
AGENT,DATABASEhoặcHYBRID) để các điểm cuối do tác nhân hỗ trợ có thể được định cấu hình phù hợp trong quá trình triển khai.
POST /api/coach/chat
- Mục đích: Tương tác trò chuyện với Huấn luyện viên cá nhân AI, xử lý các câu hỏi về huấn luyện, hỗ trợ tạo động lực và đề xuất các điều chỉnh cho bài tập (cả điều chỉnh hằng ngày và điều chỉnh tổng thể hằng tuần).
- Điều kiện kích hoạt: Người dùng gửi tin nhắn trong thẻ Huấn luyện viên hoặc nhấn vào khối đề xuất chỉnh sửa theo bối cảnh (ví dụ: "Thay thế bài tập Ép vai kiểu Arnold bằng bài tập Ép vai với tạ đôi").
- Nguồn phản hồi:
AGENT– Tận dụng Gemini với khả năng gọi hàm/công cụ có cấu trúc và truyền trực tuyến. Không trực tiếp thay đổi cơ sở dữ liệu; trả về một đề xuất theo giai đoạn để con người nằm ngoài quy trình xem xét. - Tiêu đề:
Authorization: Bearer <Firebase_ID_Token>,Content-Type: application/json
Tải trọng yêu cầu:
{
"message": "string (user input or selected prompt chip text)",
"context": {
"active_tab": "string (enum: today | week | coach)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"selected_day": "string (e.g. monday, optional)"
}
}Tải trọng phản hồi:
{
"message": "string (conversational coaching response displayed in chat bubble)",
"proposal": {
"id": "string (unique proposal ID, nullable)",
"type": "string (enum: NONE | UPDATE_DAY | UPDATE_WEEK)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (concise summary of changes, e.g. 'Swapped Arnold Press ➔ DB Shoulder Press', nullable)",
"data": "map (full proposed workout day object or weekly plan object, nullable)"
},
"memory_mutations": [
{
"operation": "string (enum: CREATE | UPDATE | DELETE)",
"target_tier": "string (enum: tier_1_staples | tier_2_physiological | tier_3_situational)",
"memory_id": "string (required for UPDATE and DELETE)",
"entry": "map (memory payload with fact, category, expires_at, etc., required for CREATE and UPDATE)",
"reason": "string (concise rationale for why memory was mutated)"
}
]
}- Ghi kép vào
weekly_chats: Ở mỗi lượt, máy chủ sẽ lưu giữ tin nhắn đến của người dùng (text) và phản hồi của huấn luyện viên (text) vào/users/{userId}/weekly_chats/{week_start_date}bằngFieldValue.arrayUnion(). - Mẫu đột biến theo giai đoạn: Trả về tải trọng đề xuất mà không sửa đổi
/weekly_planscho đến khi người dùng nhấn vào [Chấp nhận kế hoạch]. - Quản lý bộ nhớ tự động liên tục: Phát ra
memory_mutationsđể tạo, cập nhật hoặc xoá các mục khỏi hồ sơ 3 cấp của người dùng.
POST /api/workouts/log
- Mục đích: Lưu giữ một buổi tập đã hoàn thành vào kho lưu trữ tập luyện vĩnh viễn của người dùng, tính toán tổng số hiệp/số lần lặp lại cuối cùng, kiểm tra Kỷ lục cá nhân (PR) và gắn cờ ngày đã lên lịch là đã hoàn thành.
- Yếu tố kích hoạt: Người dùng nhấn vào "Hoàn tất bài tập thể dục" trên màn hình theo dõi bài tập thể dục.
- Nguồn phản hồi:
DATABASE— Giao dịch/ghi trực tiếp vào cơ sở dữ liệu Cloud Firestore.
Tải trọng yêu cầu:
{
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "string (ISO timestamp)",
"completed_at": "string (ISO timestamp)",
"duration_seconds": "number (e.g. 3120)",
"exercises_logged": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"sets": [
{
"set_number": "number (e.g. 1)",
"weight_lbs": "number (e.g. 60)",
"reps_completed": "number (e.g. 12)",
"completed": "boolean"
}
]
}
],
"user_notes": "string (optional subjective feedback)",
"perceived_exertion": "number (optional 1-10 RPE rating)"
}Tải trọng phản hồi:
{
"success": "boolean",
"log_id": "string (newly created workout log document ID)",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"new_prs": [
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"metric": "string (e.g. '10 Rep Max' or 'Estimated 1RM')",
"value": "string (e.g. '65 lbs' or '105 lbs')"
}
]
}3. Kiến trúc cơ sở dữ liệu Cloud Firestore
Bộ sưu tập: exercises
Hệ thống phân cấp: Bộ sưu tập gốc (/exercises/{exercise_id}) • Mục đích: Bách khoa toàn thư về bài tập tĩnh, bao gồm nội dung đa phương tiện, gợi ý về tư thế, yêu cầu về thiết bị và hướng dẫn.
{
"id": "string (canonical slug matching document ID, e.g. incline-db-bench-press)",
"name": "string (e.g. Incline DB Bench Press)",
"category": "string (e.g. Strength, Hypertrophy, Cardio, Mobility)",
"target_muscles": {
"primary": ["string (e.g. Upper Chest)"],
"secondary": ["string (e.g. Front Deltoids, Triceps)"]
},
"media": {
"video_url": "string (URL to demonstration video)",
"poster_url": "string (URL to video poster / thumbnail image)"
},
"instructions": ["string (step-by-step execution guidance)"],
"form_tips": ["string (critical cues and common pitfalls)"],
"equipment_required": ["string (e.g. Dumbbells, Incline Bench)"]
}Bộ sưu tập: weekly_plans
Hệ phân cấp: /users/{userId}/weekly_plans/{week_start_date} • Mục đích: "Sổ tay xoắn" chứa toàn bộ lịch tập luyện hằng tuần, chỉ số tóm tắt và các bài tập luyện riêng lẻ trong ngày.
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"theme": "string (e.g. Hypertrophy Focus & Upper Body Overload)",
"summary": {
"total_time_minutes": "number (raw minutes, e.g. 180)",
"total_volume_lbs": "number (raw tonnage in lbs, e.g. 38400)",
"frequency": "number (active workout days, e.g. 4)"
},
"suggested_edits": ["string (pre-generated AI prompt suggestion)"],
"days": {
"monday": {
"date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. Monday)",
"is_rest_day": "boolean",
"status": "string (enum: scheduled | completed | skipped)",
"completed_log_id": "string (nullable)",
"completed_at": "string (ISO timestamp, nullable)",
"title": "string (e.g. Upper Body Hypertrophy)",
"focus_tag": "string (e.g. Push & Chest Dominant)",
"duration_minutes": "number",
"estimated_volume_lbs": "number",
"coach_note": "string",
"muscle_focus": [{ "muscle": "string", "intensity": "number (0-100)" }],
"suggested_edits": ["string"],
"blocks": [
{
"label": "string (e.g. Main Compound Block)",
"exercises": [
{
"exercise_id": "string",
"name": "string",
"target_sets": "number",
"target_reps": "string or number",
"rest_seconds": "number"
}
]
}
]
}
}
}Bộ sưu tập: weekly_chats
Hệ phân cấp: /users/{userId}/weekly_chats/{week_start_date} • Mục đích: "Nhóm trò chuyện hằng tuần" chứa toàn bộ nhật ký trò chuyện của một tuần huấn luyện nhất định trong một lần đọc tài liệu.
{
"week_start_date": "string (ISO date YYYY-MM-DD, e.g. 2026-09-07)",
"messages": [
{
"id": "string (unique message ID)",
"sender": "string (enum: user | coach)",
"text": "string (conversational message body)",
"created_at": "timestamp (server timestamp)",
"proposal": {
"id": "string (proposal UID, nullable)",
"type": "string (enum: UPDATE_DAY | UPDATE_WEEK, nullable)",
"target_day": "string (e.g. monday, nullable)",
"summary_diff": "string (nullable)",
"status": "string (enum: pending | accepted | dismissed)",
"data": "map (workout or week snapshot payload, nullable)"
}
}
]
}Bộ sưu tập: workout_logs
Hệ phân cấp: /users/{userId}/workout_logs/{log_id} • Mục đích: Lưu trữ vĩnh viễn, không thể thay đổi nhật ký của các buổi tập đã hoàn thành.
{
"id": "string (document ID)",
"plan_reference": {
"week_start_date": "string (ISO date YYYY-MM-DD)",
"day_of_week": "string (e.g. monday)"
},
"title": "string (e.g. Upper Body Hypertrophy)",
"started_at": "timestamp",
"completed_at": "timestamp",
"duration_seconds": "number",
"summary": {
"total_volume_lbs": "number",
"total_sets_completed": "number",
"total_reps_completed": "number"
},
"exercises_logged": [
{
"exercise_id": "string",
"exercise_name": "string",
"sets": [
{
"set_number": "number",
"weight_lbs": "number",
"reps_completed": "number",
"completed": "boolean"
}
]
}
],
"user_notes": "string (nullable)",
"perceived_exertion": "number (1-10 RPE, nullable)"
}Bộ sưu tập: user_profiles
Hệ thống phân cấp: /users/{userId}/profile/main • Mục đích: "Biểu đồ khách hàng trực tiếp" lưu trữ hồ sơ bộ nhớ 3 cấp của người dùng.
{
"user_id": "string (document owner UID)",
"display_name": "string (e.g. Alex)",
"email": "string (e.g. alex@example.com)",
"preferences": {
"units": "string (enum: lbs | kg)",
"haptics": "boolean",
"sound": "boolean"
},
"equipment_ids": ["string (e.g. 'dumbbells', 'bench_adjustable', 'pull_up_bar')"],
"onboarding_status": "string (enum: not_started | in_progress | completed)",
"created_at": "timestamp",
"tier_1_staples": [
{
"id": "string (e.g. mem_01)",
"category": "string (enum: equipment | preference | goal | chronic_condition)",
"fact": "string (e.g. Home gym with dumbbells up to 50 lbs and adjustable bench)",
"source": "string (e.g. conversational_onboarding | manual_entry)"
}
],
"tier_2_physiological": [
{
"id": "string (e.g. mem_02)",
"body_part": "string (e.g. hamstrings, shoulder)",
"condition": "string (e.g. soreness, acute_fatigue, minor_strain)",
"fact": "string (e.g. Hamstrings are moderately sore from hill sprints)",
"effective_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
],
"tier_3_situational": [
{
"id": "string (e.g. mem_03)",
"event": "string (e.g. golf_competition, travel)",
"fact": "string (e.g. Major golf tournament on Saturday; avoid excessive arm/lat fatigue on Friday)",
"week_start_date": "string (ISO date YYYY-MM-DD)",
"expires_at": "timestamp"
}
]
}Bộ sưu tập: prs
Hệ thống phân cấp: /users/{userId}/prs/{exercise_slug} • Mục đích: Lưu trữ Kỷ lục cá nhân từ trước đến nay, điểm chuẩn ước tính cho mức tạ tối đa có thể nâng một lần và kỷ lục theo số lần lặp lại.
{
"exercise_id": "string (canonical slug, e.g. incline-db-bench-press)",
"exercise_name": "string (e.g. Incline DB Bench Press)",
"estimated_1rm": "number (calculated using Brzycki formula, e.g. 105)",
"max_weight_set": {
"weight_lbs": "number (e.g. 80)",
"reps": "number (e.g. 10)",
"achieved_at": "timestamp"
},
"rep_records": {
"10": 80,
"8": 85,
"5": 95
},
"last_updated": "timestamp"
}4. Vòng đời dữ liệu và quy trình của tác nhân
4.1 Quy trình 1: Huấn luyện, tạo bài tập và sửa đổi theo giai đoạn
- Hành động của người dùng: Gửi nội dung nhập dạng tự do hoặc nhấn vào khối chỉnh sửa theo bối cảnh được tạo sẵn.
- Tập hợp bối cảnh: Máy chủ truy xuất hồ sơ bộ nhớ 3 cấp đang hoạt động và kế hoạch của tuần hiện tại, đồng thời loại bỏ các mục bộ nhớ đã hết hạn.
- Suy luận của tác nhân: Gemini xử lý câu lệnh và ngữ cảnh bằng cách gọi công cụ/hàm có cấu trúc.
- Tự động thay đổi bộ nhớ: Thông tin thực tế đã trích xuất về người dùng được áp dụng cho
/user_profiles/{userId}và đồng bộ hoá vớiequipment_ids. - Staged Proposal Staging: Các nội dung sửa đổi được đề xuất cho bài tập thể dục sẽ được trả về cùng với
proposal.status = "pending"bên trong/weekly_chats/{week_start_date}. - Giải quyết và duy trì người dùng: Nhấn vào [Chấp nhận kế hoạch] để cam kết dữ liệu được đề xuất cho
/weekly_plansvà đánh dấu đề xuất"accepted".
4.2 Quy trình 2: Thực hiện và lưu trữ bài tập thể dục (Thông tin thực tế)
- Ghi lại buổi tập trực tiếp: Ứng dụng theo dõi trọng lượng thực tế đã nâng, số lần lặp lại đã hoàn thành và cờ hoàn thành trong trạng thái giao diện người dùng cục bộ.
- Gửi phiên: Ứng dụng gọi
POST /api/workouts/logkhi "Kết thúc bài tập thể dục". - Tính toán và xác thực tổng hợp: Máy chủ tính toán khối lượng tích luỹ của phiên và kiểm tra PR bằng công thức Brzycki.
- Lưu trữ bất biến: Ghi một bản ghi bất biến trong
/workout_logs, đánh dấu ngày"completed"trong/weekly_plansvà cập nhật/chèn PR mới vào/prs.
4.3 Quy trình 3: Tự động nhập và vô hiệu hoá bộ nhớ
- Nhập dữ liệu: Thông tin thực tế được trích xuất liên tục trong tất cả các lượt tương tác trò chuyện.
- Phân loại: Cấp 1 (Nhu cầu thiết yếu, không hết hạn), Cấp 2 (Trạng thái sinh lý, TTL 3-4 ngày), Cấp 3 (Bối cảnh tình huống, TTL dựa trên sự kiện).
- Cắt bớt và vô hiệu hoá: Các mục đã hết hạn (
current_timestamp > expires_at) sẽ được lọc ra khỏi ngữ cảnh câu lệnh và bị xoá chậm.
5. Quy tắc bảo mật của Firestore và thông số kỹ thuật của chỉ mục kết hợp
5.1 Quy tắc bảo mật (firestore.rules)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
match /profile/{document=**} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_plans/{planId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /weekly_chats/{chatId} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /prs/{exerciseSlug} { allow read, write: if request.auth != null && request.auth.uid == userId; }
match /workout_logs/{logId} {
allow read, create: if request.auth != null && request.auth.uid == userId;
allow update, delete: if false;
}
}
match /exercises/{exerciseId} {
allow read: if request.auth != null;
allow write: if false;
}
}
}5.2 Chỉ mục truy vấn kết hợp (firestore.indexes.json)
{
"indexes": [
{
"collectionGroup": "workout_logs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "completed_at", "order": "DESCENDING" }]
},
{
"collectionGroup": "weekly_plans",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "week_start_date", "order": "DESCENDING" }]
},
{
"collectionGroup": "prs",
"queryScope": "COLLECTION",
"fields": [{ "fieldPath": "last_updated", "order": "DESCENDING" }]
}
]
}6. Nhật ký thay đổi và quyết định đã khoá
| Ngày | Thành phần/Quyết định | Loại | Người khoá |
|---|---|---|---|
| Thiết lập ban đầu | Đã khởi chạy khung đặc tả trống | Thiết lập hệ thống | Nhóm |
| 2026-09-04 | Đã thêm các tập hợp Firestore: exercises và weekly_plans | Lược đồ | Nhóm |
| 2026-09-04 | Đã thêm điểm cuối POST /api/coach/chat và tập hợp chat_messages | Điểm cuối và giản đồ | Nhóm |
| 2026-09-04 | Cải tiến bộ nhớ trò chuyện thành giản đồ bộ chứa weekly_chats | Lược đồ | Nhóm |
| 2026-09-04 | Thêm điểm cuối POST /api/workouts/log và tập hợp workout_logs | Điểm cuối và giản đồ | Nhóm |
| 2026-09-04 | Đã thêm giản đồ bộ nhớ 3 cấp user_profiles và cập nhật POST /api/coach/chat bằng memory_mutations | Điểm cuối và giản đồ | Nhóm |
| 2026-09-04 | Hoàn thành Mục 1 (Phạm vi và nguyên tắc của hệ thống) và Mục 4 (Vòng đời và quy trình dữ liệu) | Hoàn tất toàn bộ thông số kỹ thuật | Nhóm |
| 2026-09-22 | Thông số kỹ thuật hài hoà: Ngăn xếp Next.js + Tailwind + Zustand, xác thực Bearer, bộ sưu tập con PR, quy tắc Firestore và slug bài tập chuẩn hoá | Sự hài hoà về thông số kỹ thuật đầy đủ | Đánh giá kiến trúc |