Thiết kế và Triển khai Chi tiết Nền tảng Đặt Vé Bảo tàng Nghệ thuật thành phố Dương Dương với SpringBoot + Vue + uniapp

Giới thiệu Bài viết này trình bày chi tiết về thiết kế và triển khai một hệ thống đặt vé trực tuyến cho Bảo tàng Nghệ thuật thành phố Dương Dương. Hệ thống được xây dựng dựa trên kiến trúc hiện đại với SpringBoot làm nền tảng backend, Vue.js cho giao diện web và uniapp cho ứng dụng di động. Giải pháp này cho phép khách hàng đặt vé trước khi đến thăm bảo tàng, giúp quản lý lượng khách hiệu quả và nâng cao trải nghiệm tham quan.

Công nghệ sử dụng Nền tảng Backend: SpringBoot SpringBoot cung cấp nhiều tính năng hữu ích giúp phát triển ứng dụng trở nên nhanh chóng và hiệu quả. Framework này tích hợp sẵn các máy chủ như Tomcat, Jetty và Undertow, cho phép triển khai ứng dụng mà không cần cấu hình thêm. Một trong những điểm mạnh nhất của SpringBoot là khả năng tự động cấu hình (auto-configuration) dựa trên các thư viện được thêm vào dự án. Điều này giúp giảm thiểu đáng kể công việc cấu hình thủ công.

SpringBoot cũng cung cấp nhiều tính năng sẵn có như Spring Data để xử lý cơ sở dữ liệu, Spring Security cho quản lý bảo mật, và hỗ trợ cho các microservices với Spring Cloud. Các tính năng này giúp nhà phát triển tập trung vào logic nghiệp vụ thay vì xử lý các vấn đề kỹ thuật phức tạp.

Giao diện Frontend: Vue.js Vue.js là một framework JavaScript hiện đại sử dụng kỹ thuật ảo DOM (virtual DOM) để tối ưu hóa thao tác với DOM. Framework này áp dụng cơ chế liên kết dữ liệu hai chiều (two-way data binding), cho phép giao diện người dùng tự động cập nhật khi dữ liệu thay đổi. Vue.js cũng hỗ trợ phát triển theo hướng component, giúp tái sử dụng mã nguồn và dễ bảo trì.

Với các tính năng như directive, component, và Vuex cho quản lý trạng thái, Vue.js cung cấp một giải pháp linh hoạt để xây dựng các giao diện người dùng phức tạp một cách hiệu quả. Đặc biệt, Vue.js có đường học tập thấp, phù hợp với cả các nhà phát triển mới bắt đầu lẫn những người có kinh nghiệm.

Layer truy cập dữ liệu: MyBatis-Plus MyBatis-Plus là một công cụ mở rộng dựa trên MyBatis, được thiết kế để đơn giản hóa việc phát triển truy cập dữ liệu. Framework này hỗ trợ nhiều hệ quản trị cơ sở dữ liệu phổ biến như MySQL, Oracle, SQL Server, PostgreSQL, v.v. MyBatis-Plus cung cấp API phong phú và các annotation giúp thực hiện các thao tác ORM mà không cần viết nhiều mã SQL thủ công.

Một tính năng nổi bật của MyBatis-Plus là khả năng tự động tạo mã nguồn, bao gồm entity class, mapper interface và file mapping XML. Điều này giúp giảm thiểu đáng kể thời gian phát triển. Ngoài ra, framework này còn hỗ trợ các tính năng hữu ích như phân trang truy vấn, truy vấn động, khóa lạc quan, và phân tích hiệu năng, giúp tối ưu hóa thao tác với cơ sở dữ liệu.

Testing hệ thống Mục đích Testing Testing là một phần không thể thiếu trong vòng đời phát triển của bất kỳ hệ thống nào. Với hệ thống đặt vé bảo tàng, mục đích chính của testing là đảm bảo chất lượng và độ tin cậy của hệ thống trước khi đưa vào sử dụng. Quá trình testing giúp phát hiện các lỗi tiềm ẩn và đảm bảo hệ thống đáp ứng đúng yêu cầu của người dùng.

Chúng tiến hành testing từ nhiều góc độ khác nhau, sử dụng các kịch bản đa dạng để mô phỏng các tình huống sử dụng thực tế. Việc testing không chỉ giúp xác minh tính đúng đắn của chức năng mà còn đảm bảo hệ thống hoạt động mượt mà và dễ sử dụng.

Testing chức năng Chúng thực hiện testing chức năng bằng phương pháp black-box, tập trung vào việc kiểm tra đầu vào và đầu ra mà không cần quan tâm đến cấu trúc nội bộ. Dưới đây là một số kịch bản testing quan trọng:

Testing chức năng đăng nhập:

  • Kiểm tra đăng nhập với thông tin hợp lệ: hệ thống cho phép truy cập
  • Kiểm tra đăng nhập với sai mật khẩu: hệ thống thông báo lỗi
  • Kiểm tra đăng nhập với tài khoản không tồn tại: hệ thống thông báo tài khoản không hợp lệ
  • Kiểm tra đăng nhập khi bỏ trống tên người dùng: hệ thống yêu cầu nhập tên
  • Kiểm tra đăng nhập khi bỏ trống mật khẩu: hệ thống yêu cầu nhập mật khẩu

Testing quản lý đặt vé:

  • Kiểm tra tạo đặt vé mới: thông tin được lưu thành công
  • Kiểm tra hủy đặt vé: hệ thống xác nhận trước khi hủy
  • Kiểm tra chỉnh sửa thông tin đặt vé: thay đổi được cập nhật
  • Kiểm tra đặt vé với ngày đã qua: hệ thống từ chối
  • Kiểm tra đặt vé vượt quá số lượng cho phép: hệ thống thông báo lỗi

Kết quả Testing Sau quá trình testing toàn diện, chúng tôi xác nhận hệ thống đáp ứng các yêu cầu thiết kế ban đầu. Các chức năng hoạt động như mong đợi và hệ thống có thể xử lý các tình huống sử dụng thực tế. Testing đã giúp phát hiện và khắc phục một số vấn đề về giao diện và xử lý lỗi, nâng cao trải nghiệm người dùng.

Ví dụ mã nguồn Dưới đây là một số đoạn mã nguồn minh họa cho hệ thống:

Controller xử lý đăng nhập:

@SkipAuth
@PostMapping("/authenticate")
public ResponseEntity<ApiResponse> authenticateUser(@RequestBody LoginRequest loginRequest) {
    UserEntity user = userService.findByUsername(loginRequest.getUsername());
    
    if (user == null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                .body(new ApiResponse(false, "Thông tin đăng nhập không chính xác"));
    }
    
    String jwtToken = jwtTokenUtil.generateToken(user);
    return ResponseEntity.ok(new ApiResponse(true, "Đăng nhập thành công")
            .addData("token", jwtToken)
            .addData("userInfo", user));
}

Service tạo JWT token:

@Service
public class JwtTokenServiceImpl implements JwtTokenService {
    
    @Value("${jwt.expiration}")
    private int jwtExpirationMs;
    
    @Override
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("roles", userDetails.getAuthorities());
        claims.put("userId", ((UserPrincipal) userDetails).getId());
        
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }
    
    @Override
    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }
}

Interceptor xác thực token:

@Component
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
    
    @Autowired
    private JwtTokenService jwtTokenService;
    
    @Autowired
    private UserService userService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", 
                "Content-Type, Authorization, X-Requested-With, Accept, Origin, Access-Control-Request-Method");
        
        if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
            response.setStatus(HttpStatus.OK.value());
            return false;
        }
        
        SkipAuth skipAuth = ((HandlerMethod) handler).getMethodAnnotation(SkipAuth.class);
        if (skipAuth != null) {
            return true;
        }
        
        final String authorizationHeader = request.getHeader("Authorization");
        
        if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
            sendError(response, "Thiếu token xác thực");
            return false;
        }
        
        String token = authorizationHeader.substring(7);
        String username;
        
        try {
            username = jwtTokenService.getUsernameFromToken(token);
        } catch (Exception e) {
            sendError(response, "Token không hợp lệ");
            return false;
        }
        
        UserEntity user = userService.findByUsername(username);
        if (user == null || !jwtTokenService.validateToken(token, user)) {
            sendError(response, "Token đã hết hạn hoặc không hợp lệ");
            return false;
        }
        
        request.setAttribute("currentUser", user);
        return true;
    }
    
    private void sendError(HttpServletResponse response, String message) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.getWriter().write(new ApiResponse(false, message).toString());
    }
}

Cơ sở dữ liệu Dưới đây là cấu trúc cơ sở dữ liệu chính cho hệ thống:

-- Bảng người dùng
CREATE TABLE `users` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL,
  `password` varchar(100) NOT NULL,
  `full_name` varchar(100) NOT NULL,
  `email` varchar(100) DEFAULT NULL,
  `phone` varchar(20) DEFAULT NULL,
  `role` varchar(20) NOT NULL DEFAULT 'USER',
  `status` tinyint(1) NOT NULL DEFAULT 1,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Bảng đặt vé
CREATE TABLE `bookings` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `booking_code` varchar(20) NOT NULL,
  `user_id` bigint(20) NOT NULL,
  `visit_date` date NOT NULL,
  `visit_time` time NOT NULL,
  `number_of_visitors` int(11) NOT NULL,
  `total_amount` decimal(10,2) NOT NULL,
  `status` varchar(20) NOT NULL DEFAULT 'PENDING',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `booking_code` (`booking_code`),
  KEY `user_id` (`user_id`),
  CONSTRAINT `bookings_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- Bảng lịch trình tham quan
CREATE TABLE `visit_schedules` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `date` date NOT NULL,
  `time_slot` time NOT NULL,
  `max_visitors` int(11) NOT NULL,
  `booked_visitors` int(11) NOT NULL DEFAULT 0,
  `status` varchar(20) NOT NULL DEFAULT 'AVAILABLE',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `date_time` (`date`,`time_slot`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Kết luận Hệ thống đặt vé Bảo tàng Nghệ thuật thành phố Dương Dương được phát triển với kiến trúc hiện đại, sử dụng các công nghệ tiên tiến để đảm bảo hiệu suất và trải nghiệm người dùng tốt nhất. Hệ thống cho phép quản lý đặt vé hiệu quả, giảm tải cho nhân viên bảo tàng và nâng cao trải nghiệm tham quan cho khách hàng.

Thẻ: SpringBoot Vue.js UniApp bảo tàng nghệ thuật đặt vé trực tuyến

Đăng vào ngày 30 tháng 6 lúc 23:24