Trong bối cảnh chuyển đổi số ngày càng sâu rộng tại các cơ sở giáo dục, việc quản lý hoạt động câu lạc bộ sinh viên đang dịch chuyển mạnh mẽ từ phương thức thủ công sang mô hình kỹ thuật số dựa trên nền tảng di động. Với sự gia tăng nhanh chóng về số lượng câu lạc bộ và tính đa dạng trong hình thức tổ chức sự kiện, các giải pháp quản lý truyền thống bộc lộ nhiều hạn chế — đặc biệt ở khâu đăng ký tham gia, theo dõi thành viên, thông báo sự kiện và kiểm soát điểm danh.
Hệ thống được thiết kế nhằm tận dụng tiềm năng của WeChat — nền tảng nhắn tin và dịch vụ tích hợp phổ biến nhất tại Trung Quốc — để xây dựng một hệ sinh thái quản lý liền mạch, dễ tiếp cận và có khả năng mở rộng. Người dùng tương tác chủ yếu qua giao diện WeChat Mini Program (hoặc webview tích hợp), trong khi toàn bộ logic nghiệp vụ và lưu trữ dữ liệu được xử lý bởi một kiến trúc phân tầng hiện đại.
Hệ thống áp dụng mô hình phân tách frontend – backend, trong đó:
- Frontend được phát triển bằng Vue 3 cùng Composition API và hỗ trợ đầy đủ TypeScript, sử dụng thư viện UI hiện đại như Element Plus để đảm bảo trải nghiệm người dùng mượt mà trên mọi thiết bị.
- Backend được xây dựng trên nền tảng Spring Boot 3.x, tích hợp Spring Security cho xác thực, Spring Validation cho kiểm tra đầu vào, và MyBatis-Plus 3.5+ để tối ưu hóa thao tác với cơ sở dữ liệu.
- Cơ sở dữ liệu sử dụng MySQL 8.0 với thiết kế chuẩn hóa, hỗ trợ ràng buộc khóa ngoại và chỉ mục tối ưu cho các truy vấn thường xuyên như tìm kiếm sự kiện theo thời gian, đếm số người đăng ký hay phân tích trạng thái tham gia.
Các chức năng cốt lõi bao gồm: đăng tải và quản lý sự kiện câu lạc bộ, quản lý hồ sơ thành viên (kèm phân quyền vai trò), đăng ký – hủy đăng ký – điểm danh trực tuyến, gửi thông báo tức thì qua WeChat Official Account hoặc Mini Program, và tổng quan thống kê hoạt động theo thời gian thực.
Bảng dữ liệu chính
Bảng activity — Lưu trữ thông tin sự kiện
| Tên cột | Kiểu dữ liệu | Mô tả |
|---|---|---|
id |
BIGINT UNSIGNED PK |
Khóa chính tự tăng |
title |
VARCHAR(64) |
Tiêu đề sự kiện |
description |
TEXT |
Mô tả chi tiết nội dung |
scheduled_at |
DATETIME |
Thời gian dự kiến bắt đầu |
venue |
VARCHAR(128) |
Địa điểm tổ chức |
capacity |
SMALLINT UNSIGNED |
Số lượng chỗ tối đa |
creator_openid |
VARCHAR(64) |
Mã định danh WeChat của người tạo |
created_at |
DATETIME DEFAULT CURRENT_TIMESTAMP |
Thời điểm tạo bản ghi |
status |
TINYINT DEFAULT 0 |
Trạng thái: 0 = Chưa diễn ra, 1 = Đang diễn ra, 2 = Đã kết thúc |
Bảng member — Hồ sơ thành viên câu lạc bộ
| Tên cột | Kiểu dữ liệu | Mô tả |
|---|---|---|
id |
BIGINT UNSIGNED PK |
Khóa chính tự tăng |
openid |
VARCHAR(64) UNIQUE |
Mã định danh duy nhất từ WeChat |
full_name |
VARCHAR(32) |
Họ và tên đầy đủ |
student_id |
VARCHAR(16) UNIQUE |
Mã sinh viên |
joined_at |
DATETIME DEFAULT CURRENT_TIMESTAMP |
Thời điểm gia nhập câu lạc bộ |
role_level |
TINYINT DEFAULT 0 |
Vai trò: 0 = Thành viên thường, 1 = Quản trị viên câu lạc bộ |
club_id |
BIGINT UNSIGNED |
Khóa ngoại tham chiếu đến bảng club |
Bảng registration — Ghi nhận đăng ký sự kiện
| Tên cột | Kiểu dữ liệu | Mô tả |
|---|---|---|
id |
BIGINT UNSIGNED PK |
Khóa chính tự tăng |
activity_id |
BIGINT UNSIGNED |
Khóa ngoại tới activity.id |
member_id |
BIGINT UNSIGNED |
Khóa ngoại tới member.id |
registered_at |
DATETIME DEFAULT CURRENT_TIMESTAMP |
Thời điểm đăng ký |
checkin_time |
DATETIME NULL |
Thời điểm điểm danh (NULL nếu chưa điểm danh) |
feedback_text |
TEXT NULL |
Phản hồi sau sự kiện (tùy chọn) |
Mẫu mã nguồn tiêu biểu
1. Lớp khởi tạo ứng dụng Spring Boot
@SpringBootApplication
@MapperScan("com.example.club.mapper")
public class ClubManagementApplication {
public static void main(String[] args) {
SpringApplication.run(ClubManagementApplication.class, args);
}
}
2. Controller quản lý thành viên với xác thực JWT
@RestController
@RequestMapping("/api/v1/members")
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
private final JwtTokenProvider tokenProvider;
@PostMapping("/login")
public ResponseEntity<ApiResponse<AuthResponse>> login(
@RequestBody @Valid LoginRequest request,
HttpServletRequest httpServletRequest) {
MemberEntity authenticated = memberService.authenticate(request.getOpenid(), request.getPassword());
String jwtToken = tokenProvider.generateToken(authenticated);
return ResponseEntity.ok(ApiResponse.success(new AuthResponse(jwtToken)));
}
@GetMapping("/profile")
@PreAuthorize("hasRole('MEMBER') or hasRole('ADMIN')")
public ResponseEntity<ApiResponse<MemberProfileDto>> getProfile(
@AuthenticationPrincipal JwtUserDetails userDetails) {
MemberProfileDto profile = memberService.fetchProfile(userDetails.getOpenid());
return ResponseEntity.ok(ApiResponse.success(profile));
}
@PutMapping("/update")
@PreAuthorize("hasRole('MEMBER') or hasRole('ADMIN')")
public ResponseEntity<ApiResponse<Void>> updateProfile(
@AuthenticationPrincipal JwtUserDetails userDetails,
@RequestBody @Valid MemberUpdateDto updateDto) {
memberService.updateProfile(userDetails.getOpenid(), updateDto);
return ResponseEntity.ok(ApiResponse.success(null));
}
}
3. Giao diện Vue 3 sử dụng Composition API để hiển thị danh sách sự kiện
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { fetchActivities } from '@/api/activity';
const activities = ref<ActivityItem[]>([]);
const loading = ref(true);
onMounted(async () => {
try {
const data = await fetchActivities({ status: 'upcoming', limit: 6 });
activities.value = data;
} finally {
loading.value = false;
}
});
</script>
<template>
<div class="activity-list">
<h2>Sự kiện sắp tới</h2>
<el-skeleton v-if="loading" :rows="3" animated />
<div v-else class="grid-container">
<ActivityCard
v-for="item in activities"
:key="item.id"
:activity="item"
/>
</div>
</div>
</template>