Kiến Trúc Hệ Thống Dịch Vụ Gia Đình Số Hóa Sử Dụng Spring Boot Và Vue.js

Giới thiệu tổng quan

Với tốc độ đô thị hóa gia tăng, nhu cầu về các dịch vụ hỗ trợ gia đình như dọn dẹp, chăm sóc trẻ em hay sửa chữa ngày càng cao. Các mô hình truyền thống thường gặp khó khăn trong việc kết nối cung-cầu do thiếu minh bạch thông tin và quy trình quản lý lạc hậu. Giải pháp là xây dựng một nền tảng web tích hợp, nơi người dùng có thể tìm kiếm, đặt lịch và đánh giá dịch vụ một cách hiệu quả.

Hệ thống được thiết kế theo kiến trúc phân tách Front-end và Back-end. Giao diện người dùng sử dụng Vue.js để đảm bảo tính linh hoạt và trải nghiệm mượt mà. Phần xử lý nghiệp vụ phía máy chủ vận hành trên framework Spring Boot, kết hợp với MyBatis-Plus để tối ưu hóa thao tác cơ sở dữ liệu. Cơ chế bảo mật đa vai trò cho phép phân quyền rõ ràng giữa khách hàng, nhân viên dịch vụ và quản trị viên.

Thiết kế cơ sở dữ liệu

Mô hình dữ liệu được chuẩn hóa nhằm đảm bảo tính toàn vẹn và hiệu suất truy vấn. Dưới đây là cấu trúc chính của các bảng dữ liệu cốt lõi:

Bảng thông tin khách hàng

Lưu trữ dữ liệu định danh của người đăng ký sử dụng dịch vụ. Trường khóa chính duy nhất xác định từng tài khoản, trong khi trạng thái tài khoản kiểm soát quyền truy cập hệ thống.

Tên trường Kiểu dữ liệu Bắt buộc Mô tả kỹ thuật
customer_id BIGINT Khóa chính tự tăng hoặc UUID
account_name VARCHAR(50) Tên đăng nhập không trùng lặp
mobile_phone VARCHAR(20) Số điện thoại liên hệ
hashed_password VARCHAR(80) Mật khẩu đã được mã hóa
role_type SMALLINT Phân loại quyền hạn (1: Khách, 2: Nhân viên, 3: Admin)
is_active TINYINT Trạng thái kích hoạt (0: Khóa, 1: Mở)
registration_date DATETIME Thời điểm tạo tài khoản

Bảng danh mục dịch vụ

Quản lý các gói dịch vụ đang cung cấp, bao gồm mức giá và trạng thái khả dụng để đặt lịch.

Tên trường Kiểu dữ liệu Bắt buộc Mô tả kỹ thuật
service_id BIGINT Định danh duy nhất cho dịch vụ
service_title VARCHAR(100) Tên gọi của hạng mục dịch vụ
category_code TINYINT Loại hình (Dọn nhà, Bảo mẫu, Sửa chữa)
unit_price DECIMAL(10,2) Mức phí cơ bản mỗi giờ/dịch vụ
assigned_staff_id BIGINT ID nhân viên phụ trách dịch vụ này
availability_status TINYINT Cho phép đặt (1: Có, 0: Tạm ngưng)
last_modified DATETIME Ngày cập nhật thông tin gần nhất

Cấu trúc ứng dụng và Mã nguồn

Hệ thống áp dụng mô hình MVC, tách biệt rõ ràng giữa Controller xử lý request, Service xử lý logic và Repository tương tác dữ liệu. Framework Spring Boot giúp khởi động ứng dụng độc lập mà không cần cấu hình server phức tạp bên ngoài.

Class khởi chạy ứng dụng

Cấu hình chính cho phép quét các component Spring và ánh xạ Mapper từ gói dữ liệu cụ thể.

package com.homeservice.platform;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication(scanBasePackages = {"com.homeservice"})
@MapperScan(basePackages = {"com.homeservice.mapper"})
public class HomeServicePlatformApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(HomeServicePlatformApplication.class, args);
    }
    
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(HomeServicePlatformApplication.class);
    }
}

API Quản lý Khách hàng

Controller này chịu trách nhiệm xử lý các yêu cầu liên quan đến tài khoản khách hàng như xác thực, đăng ký và cập nhật hồ sơ. Thay vì xử lý trực tiếp Entity trong HTTP request, ta nên sử dụng DTO cho an toàn hơn.

package com.homeservice.controller.api;

import com.homeservice.model.dto.LoginRequest;
import com.homeservice.model.entity.ClientEntity;
import com.homeservice.service.ClientService;
import com.homeservice.utils.ResultResponse;
import com.homeservice.utils.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Map;

/**
 * API Xử lý thông tin khách hàng
 */
@RestController
@RequestMapping("/api/v1/clients")
public class ClientController {

    @Autowired
    private ClientService clientSvc;

    @Autowired
    private SecurityUtils securityHelper;

    /**
     * Thực hiện đăng nhập
     */
    @PostMapping("/authenticate")
    public ResultResponse handleLogin(@RequestBody LoginRequest credentials, HttpServletRequest request) {
        QueryWrapper<ClientEntity> query = new QueryWrapper<>();
        query.eq("account_name", credentials.getUsername());
        ClientEntity currentClient = clientSvc.getOne(query);

        if (currentClient == null || !securityHelper.validatePassword(credentials.getPassword(), currentClient)) {
            return ResultResponse.fail("Thông tin đăng nhập không khớp");
        }
        
        // Kiểm tra trạng thái tài khoản
        if (currentClient.getIsActive() == 0) {
             return ResultResponse.fail("Tài khoản đã bị khóa");
        }

        String accessToken = securityHelper.generateJwtToken(currentClient.getId(), currentClient.getAccountName());
        return ResultResponse.success(Map.of("token", accessToken, "userId", currentClient.getId()));
    }

    /**
     * Đăng ký tài khoản mới
     */
    @PostMapping("/signup")
    public ResultResponse registerAccount(@RequestBody ClientEntity newClient, HttpServletRequest req) {
        QueryWrapper<ClientEntity> checkExist = new QueryWrapper<>();
        checkExist.eq("account_name", newClient.getAccountName());
        
        if (clientSvc.count(checkExist) > 0) {
            return ResultResponse.fail("Tên đăng nhập đã được sử dụng");
        }

        clientSvc.save(newClient);
        return ResultResponse.success("Đăng ký thành công");
    }

    /**
     * Lấy thông tin phiên làm việc hiện tại
     */
    @GetMapping("/profile")
    public ResultResponse getProfile(HttpServletRequest request) {
        Long userId = (Long) request.getAttribute("CURRENT_USER_ID");
        if (userId == null) return ResultResponse.fail("Phiên làm việc hết hạn");
        
        ClientEntity userDetail = clientSvc.getById(userId);
        return ResultResponse.success(userDetail);
    }

    /**
     * Danh sách phân trang (Admin)
     */
    @GetMapping("/list")
    public ResultResponse listClients(@RequestParam Map<String, Object> filters) {
        QueryWrapper<ClientEntity> condition = new QueryWrapper<>();
        // Xây dựng điều kiện lọc động dựa vào input map
        if (filters.containsKey("keyword")) {
            condition.like("account_name", filters.get("keyword"));
        }
        
        int currentPage = Integer.parseInt(filters.getOrDefault("page", "1").toString());
        int pageSize = Integer.parseInt(filters.getOrDefault("size", "10").toString());
        
        Page<ClientEntity> pageData = clientSvc.page(new Page<>(currentPage, pageSize), condition);
        return ResultResponse.success(pageData);
    }

    /**
     * Cập nhật thông tin cá nhân
     */
    @PutMapping("/{id}")
    public ResultResponse updateClientInfo(@PathVariable Long id, @RequestBody ClientEntity updatedData) {
        ClientEntity existing = clientSvc.getById(id);
        if (existing == null) {
            return ResultResponse.fail("Không tìm thấy tài khoản");
        }
        // Chỉ cập nhật các trường cho phép
        existing.setMobilePhone(updatedData.getMobilePhone());
        clientSvc.updateById(existing);
        return ResultResponse.success();
    }

    /**
     * Xóa nhiều tài khoản cùng lúc
     */
    @DeleteMapping
    public ResultResponse deleteMultiple(@RequestBody List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return ResultResponse.fail("Danh sách ID rỗng");
        }
        clientSvc.removeBatchByIds(ids);
        return ResultResponse.success("Xóa thành công");
    }
}

Kết quả trả về từ các endpoint tuân thủ chuẩn JSON đồng nhất, giúp phía Vue.js dễ dàng xử lý trạng thái tải, lỗi hoặc dữ liệu thành công.

Cơ sở dữ liệu MySQL được kết hợp với Redis để cache các thông tin dịch vụ thường xuyên được truy cập, giảm tải cho server cơ sở dữ liệu chính trong thời gian cao điểm.

Thẻ: spring-boot vuejs MyBatis-Plus mysql java-backend

Đăng vào ngày 5 tháng 6 lúc 17:12