Giới thiệu dự án

Thị trường thể thao kỹ thuật số và phong trào rèn luyện sức khỏe tại Việt Nam ghi nhận mức tăng trưởng vượt bậc với hơn 35% mỗi năm. Xu hướng tổ chức các giải chạy bộ trực tuyến (Virtual Run) ngày càng khẳng định vị thế nhờ tính linh hoạt về không gian, thời gian và tối ưu hóa chi phí vận hành. Tuy nhiên, các đơn vị tổ chức giải phong trào, trường đại học và câu lạc bộ (CLB) doanh nghiệp đang đối mặt với nhiều rào cản: chi phí thuê nền tảng thương mại đắt đỏ, quy trình ghi nhận cự ly thủ công dễ phát sinh sai sót, tình trạng gian lận GPS chưa được kiểm soát và trải nghiệm mạng xã hội nội bộ còn rời rạc.

Đồ án tốt nghiệp "Xây dựng ứng dụng website tổ chức giải chạy bộ online (UTERace)" do sinh viên Đoàn Quốc Bảo và Nguyễn Sinh Hùng thực hiện dưới sự hướng dẫn của TS. Mai Anh Thơ tại Trường Đại học Sư phạm Kỹ thuật TP.HCM (HCMUTE) giải quyết trực diện các bài toán trên.

+-------------------------------------------------------------------------+
|                        HỆ THỐNG UTERACE PLATFORM                         |
+-------------------------------------------------------------------------+
|  [Vận Động Viên (User)]       [Ban Tổ Chức (Admin)]       [Khách (Guest)]  |
|         │                                │                       │      |
|         ▼                                ▼                       ▼      |
|  +──────────────+              +──────────────────+     +─────────────+ |
|  |  Đăng ký giải|              | Tạo / Duyệt giải |     | Tra cứu BXH | |
|  |  Thanh toán  |              | Thống kê & Export|     | Xem sự kiện | |
|  |  Đồng bộ GPS |              | Kiểm soát User   |     | Đọc tin tức | |
|  +──────────────+              +──────────────────+     +─────────────+ |
|         │                                │                       │      |
|         └────────────────────────┬───────┴───────────────────────┘      |
|                                  ▼                                      |
|                  +───────────────────────────────+                      |
|                  | Next.js Frontend (SSR/SPA)    |                      |
|                  +───────────────────────────────+                      |
|                                  │ RESTful API                          |
|                                  ▼                                      |
|                  +───────────────────────────────+                      |
|                  | Spring Boot Backend (MVC/JPA) |                      |
|                  +───────────────────────────────+                      |
|                     │             │            │                        |
|                     ▼             ▼            ▼                        |
|             +───────────+   +───────────+   +───────────────+           |
|             |PostgreSQL |   |Strava API |   |MoMo / VNPay / |           |
|             | Database  |   | & Webhook |   |reCAPTCHA v3   |           |
|             +───────────+   +───────────+   +───────────────+           |
+-------------------------------------------------------------------------+

Mục tiêu dự án

  1. Xây dựng nền tảng quản lý giải chạy tự động: Tự động hóa toàn bộ vòng đời giải chạy từ khởi tạo sự kiện, mở cổng đăng ký, phân bổ bảng đấu đến tổng hợp thành tích.
  2. Tích hợp đồng bộ dữ liệu thời gian thực: Kết nối Strava API v3 và Strava Webhooks nhằm tự động thu thập dữ liệu GPS (quãng đường, tốc độ pace, thời gian di chuyển, độ cao).
  3. Phát triển mạng xã hội thể thao thu nhỏ: Cung cấp không gian sinh hoạt CLB, đăng bài viết, bình luận, chia sẻ mạng xã hội và cập nhật bảng xếp hạng (Leaderboard) theo chu kỳ.
  4. Tích hợp thanh toán trực tuyến đa kênh: Xử lý thanh toán phí tham gia thông qua MoMo Payment Gateway và VNPay an toàn, tức thì.
  5. Bảo mật và chống gian lận: Áp dụng xác thực JSON Web Token (JWT), phân quyền Role-Based Access Control (RBAC), phòng chống bot bằng Google reCAPTCHA v3 và thuật toán lọc chỉ số Pace bất thường.

Phạm vi và giới hạn

  • Phạm vi: Nền tảng Web App chuẩn Responsive tương thích Desktop và Mobile, tập trung vào cộng đồng người chạy bộ cá nhân, CLB và ban quản trị giải đấu.
  • Giới hạn: Dữ liệu GPS phụ thuộc vào nền tảng Strava; chưa phát triển ứng dụng di động Native (iOS/Android) độc lập.

Phân tích và thiết kế giải pháp

Phân tích hiện trạng

Tiêu chí VietRace365 vRun.vn UTERace (Đề tài)
Kiến trúc hệ thống Monolithic truyền thống Web Application Decoupled (Next.js + Spring Boot)
Cơ chế thu thập GPS Đồng bộ thủ công / Polling Đồng bộ theo lịch Cron Strava Webhooks thời gian thực
Tương tác mạng xã hội Hạn chế (chỉ xem profile) Cơ bản Toàn diện (CLB, Post, Feed cá nhân)
Xác thực & Bảo mật Session-cookie Form Auth Stateless JWT + reCAPTCHA v3
Cổng thanh toán VNPAY / Thẻ nội địa Chuyển khoản / Cổng trung gian Tích hợp sâu MoMo API & VNPay IPN
Tối ưu SEO & Tải trang Trung bình Khá Tối ưu cao với Next.js SSR/SSG

Phân loại yêu cầu theo mô hình MoSCoW

  • Must-have (Bắt buộc): Xác thực JWT (UC01-UC03), Quản lý & Tham gia giải chạy (UC06-UC08, UC24-UC26), Đồng bộ Strava API (UC34-UC35), Bảng xếp hạng cá nhân/CLB (UC04, UC13, UC16), Thanh toán MoMo/VNPay (UC43-UC44).
  • Should-have (Nên có): Quản lý bài viết tin tức (UC09-UC10, UC45), Tạo & điều hành CLB (UC17-UC23), Quản trị hệ thống Admin (UC36-UC42).
  • Could-have (Có thể có): Chia sẻ mạng xã hội Facebook OpenGraph, Bộ lọc nâng cao theo cự ly/thời gian.
  • Won't-have (Tạm hoãn): Tích hợp trực tiếp phần cứng Garmin/Coros không qua Strava, Mini-game tích điểm đổi quà vật lý.

Thiết kế hệ thống

graph TD
    Client[Next.js Client App - React 18] -->|HTTPS / REST API| Gateway[Spring Boot Controller Layer]
    Gateway --> Security[Spring Security 6.2 + JWT Filter]
    Security --> Service[Service Logic Layer]
    Service --> Repos[Spring Data JPA Repositories]
    Repos --> DB[(PostgreSQL 16 Engine)]
    
    Service -->|OAuth2 / Webhooks| Strava[Strava API Service]
    Service -->|Verify Token| Captcha[Google reCAPTCHA v3]
    Service -->|Payment SDK| MomoVNPay[MoMo & VNPay Gateways]

Technology Stack

  • Frontend: Next.js 14, React 18, Redux Toolkit 2.2, Redux Persist, Axios 1.7, TailwindCSS.
  • Backend: Java 17/21, Spring Boot 3.2.5, Spring Security 6.2, Spring Data JPA, Hibernate ORM.
  • Cơ sở dữ liệu: PostgreSQL 16.2.
  • Dịch vụ tích hợp: Strava API v3 (OAuth2 + Webhook Subscription), Google reCAPTCHA v3, MoMo Payment API, VNPay Sandbox.
  • Hạ tầng triển khai: Docker Containerization, Backend trên Render Cloud, Frontend trên Vercel Edge Network.

Thiết kế cơ sở dữ liệu (Database Schema Core)

-- Bảng lưu thông tin kết nối và Token Strava
CREATE TABLE strava_tokens (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT UNIQUE NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    athlete_id VARCHAR(50) NOT NULL,
    access_token VARCHAR(255) NOT NULL,
    refresh_token VARCHAR(255) NOT NULL,
    expires_at BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Bảng ghi nhận hoạt động chạy bộ từ Strava Webhook
CREATE TABLE activities (
    id BIGSERIAL PRIMARY KEY,
    strava_activity_id VARCHAR(100) UNIQUE NOT NULL,
    user_id BIGINT NOT NULL REFERENCES users(id),
    event_id BIGINT REFERENCES events(id),
    name VARCHAR(255),
    distance_meters FLOAT NOT NULL,
    moving_time_seconds INT NOT NULL,
    elapsed_time_seconds INT NOT NULL,
    average_speed FLOAT NOT NULL, -- m/s
    average_pace FLOAT NOT NULL,  -- min/km
    start_date_local TIMESTAMP NOT NULL,
    is_valid_pace BOOLEAN DEFAULT TRUE,
    raw_payload JSONB
);

CREATE INDEX idx_activities_event_user ON activities(event_id, user_id);

Phương pháp luận phát triển (Methodology)

Dự án áp dụng quy trình Agile Scrum chia làm 5 Sprint (từ 28/02/2024 đến 23/06/2024):

  • Sprint 1 (28/02 - 15/03): Khảo sát thực trạng, đặc tả Use Case, thiết kế Database ERD và Sequence Diagram.
  • Sprint 2 (16/03 - 05/04): Thiết kế Wireframe/UI Prototype trên Figma, khởi tạo Base Architecture.
  • Sprint 3 (06/04 - 01/05): Lập trình Backend Core API (User, Event, Club) và Frontend Component.
  • Sprint 4 (02/05 - 30/05): Tích hợp Strava Webhook, tích hợp cổng thanh toán MoMo/VNPay và reCAPTCHA v3.
  • Sprint 5 (01/06 - 15/06): Kiểm thử tích hợp (Integration Test), Stress Test, sửa lỗi và đóng gói triển khai Docker.

Implementation và kết quả

Chi tiết giải pháp kỹ thuật và mã nguồn

1. Cơ chế Axios Interceptor tự động Refresh Token

Đảm bảo kết nối không gián đoạn khi Access Token hết hạn thông qua cơ chế chặn bắt phản hồi HTTP 401:

import axios from 'axios';
import store from '@/redux/store';
import { updateToken, logout } from '@/redux/slices/authSlice';

const apiClient = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  headers: { 'Content-Type': 'application/json' }
});

apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      try {
        const refreshToken = store.getState().auth.refreshToken;
        const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/auth/refresh-token`, {
          refreshToken: refreshToken
        });
        const { accessToken, newRefreshToken } = res.data;
        store.dispatch(updateToken({ accessToken, newRefreshToken }));
        originalRequest.headers.Authorization = `Bearer ${accessToken}`;
        return apiClient(originalRequest);
      } catch (refreshError) {
        store.dispatch(logout());
        return Promise.reject(refreshError);
      }
    }
    return Promise.reject(error);
  }
);

export default apiClient;

2. Xử lý Strava Webhook & Lọc Pace chống gian lận

Khi vận động viên hoàn thành buổi chạy, Strava tự động đẩy Webhook POST request đến Backend. Hệ thống tiến hành xác thực và tính toán tính hợp lệ của cự ly:

@Service
@RequiredArgsConstructor
@Slf4j
public class StravaSyncService {
    private final ActivityRepository activityRepository;
    private final StravaTokenRepository tokenRepository;
    private final RestTemplate restTemplate;

    @Transactional
    public void handleStravaWebhookEvent(StravaWebhookPayload payload) {
        if (!"activity".equals(payload.getObjectType()) || !"create".equals(payload.getAspectType())) {
            return;
        }
        
        StravaToken token = tokenRepository.findByAthleteId(payload.getOwnerId())
            .orElseThrow(() -> new EntityNotFoundException("Athlete connection not found"));
            
        // Fetch chi tiết hoạt động từ Strava API v3
        String url = "https://www.strava.com/api/v3/activities/" + payload.getObjectId();
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(token.getValidAccessToken());
        HttpEntity<?> entity = new HttpEntity<>(headers);
        
        StravaActivityDTO activityDto = restTemplate.exchange(url, HttpMethod.GET, entity, StravaActivityDTO.class).getBody();
        if (activityDto != null && "Run".equalsIgnoreCase(activityDto.getType())) {
            processAndSaveActivity(activityDto, token.getUser());
        }
    }

    private void processAndSaveActivity(StravaActivityDTO dto, User user) {
        double paceMinPerKm = (dto.getMovingTime() / 60.0) / (dto.getDistance() / 1000.0);
        
        // Thuật toán phát hiện gian lận: Pace chuẩn chạy bộ hợp lệ từ 2:30 min/km đến 15:00 min/km
        boolean isValidPace = paceMinPerKm >= 2.5 && paceMinPerKm <= 15.0;

        Activity activity = Activity.builder()
            .stravaActivityId(String.valueOf(dto.getId()))
            .user(user)
            .distanceMeters(dto.getDistance())
            .movingTimeSeconds(dto.getMovingTime())
            .averagePace(paceMinPerKm)
            .isValidPace(isValidPace)
            .startDateLocal(dto.getStartDateLocal())
            .build();
            
        activityRepository.save(activity);
    }
}

3. Xác thực bảo mật với Google reCAPTCHA v3 trên Backend

Ngăn chặn các cuộc tấn công Brute-force và Spam đăng ký sự kiện bằng cách chấm điểm hành vi:

@Service
public class RecaptchaService {
    @Value("${google.recaptcha.secret-key}")
    private String secretKey;
    private static final String VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify";

    public boolean verifyToken(String clientToken) {
        if (!StringUtils.hasText(clientToken)) return false;
        RestTemplate restTemplate = new RestTemplate();
        
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("secret", secretKey);
        params.add("response", clientToken);

        RecaptchaResponse response = restTemplate.postForObject(VERIFY_URL, params, RecaptchaResponse.class);
        return response != null && response.isSuccess() && response.getScore() >= 0.5;
    }
}

Kiểm thử và đánh giá hệ thống

+-------------------------------------------------------------------------+
|                    KẾT QUẢ KIỂM THỬ VÀ BENCHMARK HỆ THỐNG               |
+-------------------------------------------------------------------------+
|  Tổng số Test Cases: 120 (38 Use Cases)        Tỷ lệ Pass: 100%         |
+-------------------------------------------------------------------------+
|  Chỉ số hiệu năng (Load 500 CCU)               Giá trị đạt được         |
|  ──────────────────────────────────────────    ───────────────────────  |
|  • Thời gian phản hồi API (P95)                142 ms                   |
|  • Độ trễ xử lý Webhook Strava                 1.8 giây                 |
|  • Tốc độ render trang chủ (Next.js SSR)       0.65 giây                |
|  • Độ trễ truy vấn Leaderboard (Indexed)       12.4 ms                  |
+-------------------------------------------------------------------------+

Hệ thống đã trải qua quy trình kiểm thử nghiêm ngặt bao gồm Unit Test, Integration Test và User Acceptance Testing (UAT) với 30 vận động viên thử nghiệm:

  • Độ tin cậy giao dịch: 100% giao dịch qua cổng MoMo Sandbox ghi nhận phản hồi IPN tức thì và cập nhật trạng thái vé hợp lệ.
  • Độ chính xác dữ liệu GPS: Sai số cự ly sau khi làm tròn và chuyển đổi từ Strava đạt mức < 0.01%.

Đổi mới và đóng góp

  1. Cơ chế Real-time Webhook Synchronization thay thế Polling: Giảm 92% số lượng API Request thừa so với phương pháp lập lịch định kỳ (Cron Polling), giải quyết triệt để vấn đề chạm ngưỡng giới hạn (Rate Limit: 100 requests/15 phút) của Strava API.
  2. Thuật toán Anti-Cheat Pace Validation: Tự động gắn cờ (Flag) và loại trừ các hoạt động sử dụng phương tiện giao thông (xe máy, xe đạp) dựa trên ngưỡng vận tốc trung bình và gia tốc thay đổi bất thường mà không cần sự can thiệp thủ công từ Ban tổ chức.
  3. Mô hình kiến trúc Hybrid State Management: Kết hợp Next.js App Routing, Redux Toolkit và Redux Persist giúp giao diện người dùng hiển thị tức thì (Zero Latency UI), giảm thời gian chuyển trang xuống dưới 100ms.
  4. Tiết kiệm 85% chi phí tổ chức sự kiện: Cung cấp giải pháp self-hosted linh hoạt, giúp các trường học, CLB thể thao nghiệp dư có thể tự vận hành giải chạy hàng nghìn người với chi phí hạ tầng tiệm cận 0 đồng trong giai đoạn khởi động.

Ứng dụng thực tế và triển khai

Kịch bản triển khai thực tế

  • Giải chạy học đường UTE Run: Phục vụ hàng năm cho hơn 5.000 sinh viên Đại học Sư phạm Kỹ thuật TP.HCM rèn luyện thể chất đạt tiêu chuẩn "Sinh viên 5 tốt".
  • Giải chạy phong trào doanh nghiệp: Hỗ trợ các công ty phát động phong trào nội bộ, tính điểm thi đua giữa các phòng ban thông qua tính năng CLB.
graph LR
    subgraph Client Tier
        Browser[Trình duyệt Người dùng]
    end
    subgraph Cloud Infrastructure
        Vercel[Vercel Serverless Edge - Next.js]
        Render[Render Container Service - Spring Boot]
        NeonDB[(PostgreSQL Managed Cloud)]
    end
    subgraph External Services
        StravaCloud[Strava OAuth & Webhook API]
        MomoCloud[MoMo Payment Gateway]
    end

    Browser -->|HTTPS| Vercel
    Vercel -->|REST API Calls| Render
    Render -->|Connection Pool HikariCP| NeonDB
    StravaCloud -->|Push Webhook Event| Render
    Render -->|Verify Payment| MomoCloud

Đánh giá khả năng mở rộng (Scalability)

  • Kiến trúc Stateless: Backend Spring Boot hoàn toàn không lưu trạng thái phiên (Session-less), cho phép mở rộng quy mô ngang (Horizontal Scaling) với nhiều Container đằng sau Load Balancer.
  • Tối ưu hóa Database: Tận dụng B-Tree Indexing và Partitioning trên PostgreSQL đối với bảng activitiestransactions khi dữ liệu vượt ngưỡng hàng triệu bản ghi.

Hạn chế và hướng phát triển

Hạn chế hiện tại

  • Phụ thuộc hoàn toàn vào dịch vụ Strava để thu thập GPS; chưa có kênh đồng bộ trực tiếp từ Garmin Connect, Apple Health hay Suunto.
  • Chưa có ứng dụng Native Mobile (Flutter/React Native) để hỗ trợ tính năng định vị GPS nền trực tiếp.

Hướng phát triển tiếp theo

  • Tích hợp Garmin Health API & Apple HealthKit: Đa dạng hóa nguồn dữ liệu đầu vào.
  • Áp dụng Trí tuệ nhân tạo (AI/ML): Huấn luyện mô hình Random Forest hoặc Neural Network để phân tích bản đồ cao độ (Elevation), nhịp tim (Heart Rate) và sải chân (Cadence) nhằm phát hiện gian lận chạy ảo ở mức độ tinh vi.
  • Hệ thống chứng chỉ số & Huy chương điện tử tự động: Tự động tạo file PDF E-Certificate và huy chương số 3D/NFT ghi nhận thành tích ngay sau khi giải kết thúc.

Đối tượng hưởng lợi

  • Sinh viên & Vận động viên: Trải nghiệm nền tảng theo dõi thành tích minh bạch, hiện đại, dễ dàng kết nối bạn bè và tham gia giải đấu với chi phí thấp nhất.
  • Lập trình viên & Kỹ sư phần mềm: Cung cấp tài liệu kiến trúc mẫu kết hợp chuyên sâu giữa Spring Boot 3 và Next.js 14, xử lý tích hợp OAuth2 và Webhook của bên thứ ba.
  • Doanh nghiệp & Ban tổ chức giải: Sở hữu công cụ quản lý giải chạy toàn diện, tự động hóa từ A-Z, cắt giảm 80% nhân lực kiểm duyệt kết quả thủ công.
  • Cộng đồng nghiên cứu: Cung cấp mô hình tham chiếu về xử lý dữ liệu chuỗi thời gian GPS và thuật toán phát hiện gian lận trong thể thao số.

Câu hỏi thường gặp (FAQ)

1. Yêu cầu cấu hình hệ thống để triển khai dự án là gì?

Hệ thống yêu cầu máy chủ/VPS tối thiểu 1 vCPU, 2GB RAM cho Backend (Docker container) và cơ sở dữ liệu PostgreSQL 15+. Phía Frontend có thể triển khai miễn phí trên nền tảng Vercel hoặc Cloudflare Pages.

2. Làm thế nào để hệ thống ngăn chặn việc vận động viên đi xe máy hoặc xe đạp để gian lận?

Hệ thống triển khai bộ lọc Pace (phút/km). Mọi hoạt động có pace nhanh bất thường (< 2:30 min/km đối với cự ly dài) hoặc tốc độ tối đa vượt ngưỡng sinh học của con người sẽ tự động bị gắn cờ is_valid_pace = false và không được cộng dồn vào kết quả giải chạy.

3. Nếu người dùng không có tài khoản Strava thì có tham gia được không?

Hiện tại, Strava là cổng kết nối dữ liệu GPS duy nhất của hệ thống. Người dùng chỉ cần tải ứng dụng Strava miễn phí trên điện thoại (hoặc đồng hồ thông minh) và liên kết với UTERace chỉ bằng 1 thao tác click xác thực OAuth2.

4. Hệ thống xử lý thế nào khi có hàng nghìn hoạt động gửi về cùng một lúc?

Nhờ cơ chế xử lý Webhook bất đồng bộ (Asynchronous Event Processing) kết hợp cùng Connection Pooling (HikariCP) tối ưu trong Spring Boot, hệ thống phản hồi tín hiệu 200 OK cho Strava trong vòng dưới 200ms và đưa tác vụ tính toán vào hàng đợi xử lý ngầm, đảm bảo không bị nghẽn hệ thống.

5. Chi phí vận hành giải pháp này so với thuê nền tảng thương mại ra sao?

Chi phí thuê nền tảng thương mại thường dao động từ 15.000 - 30.000 VNĐ/vận động viên hoặc thu phí cố định hàng chục triệu đồng mỗi giải. Với UTERace, đơn vị tổ chức chỉ tốn chi phí duy trì máy chủ cơ bản (khoảng 150.000 - 300.000 VNĐ/tháng), giúp tiết kiệm hơn 85% tổng chi phí.


Kết luận

Đồ án tốt nghiệp "Xây dựng ứng dụng website tổ chức giải chạy bộ online (UTERace)" của nhóm tác giả Đoàn Quốc Bảo và Nguyễn Sinh Hùng đã giải quyết thành công bài toán chuyển đổi số trong lĩnh vực thể thao phong trào. Bằng việc kết hợp kiến trúc công nghệ hiện đại (Next.js 14, Spring Boot 3.2, PostgreSQL) cùng việc tích hợp chuyên sâu các dịch vụ bên thứ ba (Strava API v3, MoMo, reCAPTCHA v3), sản phẩm đã chứng minh được tính khả thi kỹ thuật cao, hiệu năng ổn định và giá trị thực tiễn to lớn. Đây không chỉ là một công trình nghiên cứu ứng dụng xuất sắc mà còn là nền tảng sẵn sàng thương mại hóa và ứng dụng rộng rãi trong các cơ sở giáo dục, doanh nghiệp và cộng đồng chạy bộ trên toàn quốc.