Hệ thống đặt vé rạp chiếu phim được xây dựng trên kiến trúc phân tầng rõ ràng: backend dựa trên Spring Boot (Java 8+), sử dụng MySQL 5.7+ làm cơ sở dữ liệu quan hệ; frontend được phát triển bằng Vue 3 kết hợp Element Plus cho giao diện người dùng chuyên nghiệp. Dự án hỗ trợ đầy đủ chức năng quản trị và trải nghiệm người dùng thông qua hai cổng vào riêng biệt.
Môi trường triển khai:
- Java JDK 8 trở lên
- MySQL 5.7 hoặc mới hơn
- Node.js 14+ (cho phần frontend)
Công cụ phát triển đề xuất:
- Backend: IntelliJ IDEA, Eclipse hoặc VS Code với plugin Java
- Frontend: VS Code hoặc WebStorm với hỗ trợ Vue và ESLint
Cấu trúc thư mục chính:
server/: Dự án Spring Boot (Maven), chứa lớp khởi độngApplication.javaclient-admin/: Giao diện quản trị (Vue + Element Plus), chạy bằngnpm run devclient-user/: Giao diện người dùng cuối (Vue + Element Plus), chạy bằngnpm run serve
Tài khoản mặc định:
- Quản trị viên:
admin/123456 - Người dùng thường:
user/123456
Một số đoạn mã logic đặc trưng đã được cải tiến:
@RestController
@RequestMapping("/api/roles")
public class RoleManagementController {
@Autowired
private RoleService roleService;
@GetMapping("/{roleId}")
public ResponseEntity<ApiResponse> fetchRoleDetails(@PathVariable Long roleId) {
return ResponseEntity.ok(ApiResponse.success(roleService.retrieveById(roleId)));
}
@PostMapping
public ResponseEntity<ApiResponse> createNewRole(@Valid @RequestBody RoleForm form) {
return ResponseEntity.ok(ApiResponse.success(roleService.create(form)));
}
@PatchMapping
public ResponseEntity<ApiResponse> modifyRole(@Valid @RequestBody RoleForm form) {
return ResponseEntity.ok(ApiResponse.success(roleService.update(form)));
}
@DeleteMapping("/batch")
public ResponseEntity<ApiResponse> removeRoles(@RequestBody Long[] roleIds) {
roleService.deleteBatch(roleIds);
return ResponseEntity.ok(ApiResponse.success("Xóa thành công"));
}
@PostMapping("/{roleId}/permissions")
public ResponseEntity<ApiResponse> assignPermissions(
@PathVariable Long roleId,
@RequestBody Set<Long> permissionIds) {
roleService.bindPermissions(roleId, permissionIds);
return ResponseEntity.ok(ApiResponse.success("Phân quyền hoàn tất"));
}
}
@RestController
@RequestMapping("/api/sessions")
public class ScreeningController {
@Autowired
private ScreeningService screeningService;
@GetMapping
public ResponseEntity<ApiResponse> listScreenings(@ModelAttribute ScreeningQuery query) {
Pageable pageable = PageRequest.of(
query.getPage() - 1,
query.getSize(),
Sort.by(query.getSortField()).descending()
);
return ResponseEntity.ok(ApiResponse.success(screeningService.search(query, pageable)));
}
@PostMapping
public ResponseEntity<ApiResponse> scheduleNewScreening(@Valid @RequestBody ScreeningDto dto) {
Screening saved = screeningService.schedule(dto);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(saved));
}
@PutMapping("/{id}")
public ResponseEntity<ApiResponse> updateScreening(
@PathVariable Long id,
@Valid @RequestBody ScreeningDto dto) {
dto.setId(id);
return ResponseEntity.ok(ApiResponse.success(screeningService.update(dto)));
}
}
@RestController
@RequestMapping("/api/upload")
public class MediaUploadController {
private static final Map<String, String> UPLOAD_PATHS = Map.of(
"avatar", "static/uploads/users/",
"poster", "static/uploads/movies/",
"theater", "static/uploads/theaters/",
"actor", "static/uploads/actors/"
);
@PostMapping("/{type}")
public ResponseEntity<ApiResponse> uploadMedia(
@PathVariable String type,
@RequestParam MultipartFile file) throws IOException {
if (!UPLOAD_PATHS.containsKey(type)) {
throw new IllegalArgumentException("Loại tệp không được hỗ trợ: " + type);
}
String baseDir = ResourceUtils.getFile("classpath:").getAbsolutePath() +
File.separator + UPLOAD_PATHS.get(type);
Path uploadPath = Paths.get(baseDir);
if (!Files.exists(uploadPath)) {
Files.createDirectories(uploadPath);
}
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
Path targetPath = uploadPath.resolve(fileName);
file.transferTo(targetPath);
String publicUrl = "/uploads/" + type + "/" + fileName;
return ResponseEntity.ok(ApiResponse.success(Map.of("url", publicUrl)));
}
}
Hệ thống áp dụng xác thực JWT để kiểm soát truy cập, tích hợp cơ chế CORS toàn cục nhằm đảm bảo tương thích khi triển khai tách biệt frontend/backend. Các thao tác thanh toán và hủy vé được xử lý đồng bộ với trạng thái chỗ ngồi trong từng suất chiếu, đảm bảo tính nhất quán dữ liệu thời gian thực.