0

[Java Backend Zero to Hello] [Phase 2] BÀI 2.12: BÀI TẬP CUỐI PHASE 2

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 2: Java Core nâng cao 📖 Nội dung: BÀI 2.12: BÀI TẬP CUỐI PHASE 2 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


BÀI 2.12: BÀI TẬP CUỐI PHASE 2

Mục tiêu

  • Tổng hợp kiến thức Phase 2
  • Thực hành với project thực tế
  • Áp dụng Generic, Lambda, Stream, Optional, Date/Time, đa luồng

ĐỀ BÀI: HỆ THỐNG XỬ LÝ ĐƠN HÀNG

Xây dựng ứng dụng console xử lý đơn hàng với các yêu cầu:

Yêu cầu chức năng

  1. Đọc danh sách đơn hàng từ file CSV
  2. Lọc đơn hàng theo trạng thái, khoảng thời gian
  3. Thống kê doanh thu theo ngày/tháng
  4. Tìm khách hàng VIP (tổng giá trị > 10 triệu)
  5. Xuất báo cáo ra file
  6. Xử lý song song nhiều file

CẤU TRÚC PROJECT

order-system/
├── src/
│   ├── model/
│   │   ├── Order.java
│   │   ├── OrderItem.java
│   │   ├── OrderStatus.java
│   │   └── Customer.java
│   ├── service/
│   │   ├── OrderService.java
│   │   ├── ReportService.java
│   │   └── FileService.java
│   └── Main.java
├── data/
│   └── orders.csv
└── output/
    └── report.txt

CODE MẪU

1. Model

// OrderStatus.java
public enum OrderStatus {
    PENDING("Đang chờ"),
    CONFIRMED("Đã xác nhận"),
    SHIPPED("Đang giao"),
    DELIVERED("Đã giao"),
    CANCELLED("Đã hủy");

    private final String displayName;

    OrderStatus(String displayName) {
        this.displayName = displayName;
    }

    public String getDisplayName() {
        return displayName;
    }
}
// OrderItem.java
public record OrderItem(
    String productId,
    String productName,
    int quantity,
    double price
) {
    public double getTotal() {
        return quantity * price;
    }
}
// Order.java
public record Order(
    String orderId,
    String customerId,
    String customerName,
    List<OrderItem> items,
    OrderStatus status,
    LocalDateTime createdAt
) {
    public double getTotal() {
        return items.stream()
            .mapToDouble(OrderItem::getTotal)
            .sum();
    }

    public int getItemCount() {
        return items.stream()
            .mapToInt(OrderItem::quantity)
            .sum();
    }
}
// Customer.java
public record Customer(
    String id,
    String name,
    String email,
    String phone
) {}

2. Service

// OrderService.java
public class OrderService {
    private final List<Order> orders;

    public OrderService(List<Order> orders) {
        this.orders = new ArrayList<>(orders);
    }

    // Lọc theo trạng thái
    public List<Order> findByStatus(OrderStatus status) {
        return orders.stream()
            .filter(o -> o.status() == status)
            .collect(Collectors.toList());
    }

    // Lọc theo khoảng thời gian
    public List<Order> findByDateRange(LocalDate start, LocalDate end) {
        return orders.stream()
            .filter(o -> {
                LocalDate orderDate = o.createdAt().toLocalDate();
                return !orderDate.isBefore(start) && !orderDate.isAfter(end);
            })
            .collect(Collectors.toList());
    }

    // Tổng doanh thu
    public double getTotalRevenue() {
        return orders.stream()
            .filter(o -> o.status() == OrderStatus.DELIVERED)
            .mapToDouble(Order::getTotal)
            .sum();
    }

    // Doanh thu theo ngày
    public Map<LocalDate, Double> getRevenueByDate() {
        return orders.stream()
            .filter(o -> o.status() == OrderStatus.DELIVERED)
            .collect(Collectors.groupingBy(
                o -> o.createdAt().toLocalDate(),
                Collectors.summingDouble(Order::getTotal)
            ));
    }

    // Top khách hàng
    public Map<String, Double> getTopCustomers(int limit) {
        Map<String, Double> customerRevenue = orders.stream()
            .filter(o -> o.status() == OrderStatus.DELIVERED)
            .collect(Collectors.groupingBy(
                Order::customerId,
                Collectors.summingDouble(Order::getTotal)
            ));

        return customerRevenue.entrySet().stream()
            .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
            .limit(limit)
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (a, b) -> a,
                LinkedHashMap::new
            ));
    }

    // Khách hàng VIP
    public List<String> getVipCustomers(double threshold) {
        return getTopCustomers(Integer.MAX_VALUE).entrySet().stream()
            .filter(e -> e.getValue() >= threshold)
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    }

    // Sản phẩm bán chạy
    public Map<String, Long> getBestSellingProducts(int limit) {
        Map<String, Long> productSales = orders.stream()
            .filter(o -> o.status() == OrderStatus.DELIVERED)
            .flatMap(o -> o.items().stream())
            .collect(Collectors.groupingBy(
                OrderItem::productId,
                Collectors.summingLong(OrderItem::quantity)
            ));

        return productSales.entrySet().stream()
            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
            .limit(limit)
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue,
                (a, b) -> a,
                LinkedHashMap::new
            ));
    }
}
// FileService.java
public class FileService {

    public List<Order> readOrdersFromCsv(String filePath) throws IOException {
        return Files.lines(Paths.get(filePath))
            .skip(1)  // Bỏ header
            .map(this::parseOrder)
            .filter(Optional::isPresent)
            .map(Optional::get)
            .collect(Collectors.toList());
    }

    private Optional<Order> parseOrder(String line) {
        try {
            String[] parts = line.split(",");
            // Format: orderId,customerId,customerName,status,createdAt,items
            String orderId = parts[0];
            String customerId = parts[1];
            String customerName = parts[2];
            OrderStatus status = OrderStatus.valueOf(parts[3]);
            LocalDateTime createdAt = LocalDateTime.parse(parts[4]);

            // Parse items (giả sử format đơn giản)
            List<OrderItem> items = parseItems(parts[5]);

            return Optional.of(new Order(
                orderId, customerId, customerName, items, status, createdAt
            ));
        } catch (Exception e) {
            System.err.println("Lỗi parse: " + line);
            return Optional.empty();
        }
    }

    private List<OrderItem> parseItems(String itemsStr) {
        // itemsStr: "productId:quantity:price|productId:quantity:price"
        return Arrays.stream(itemsStr.split("\\|"))
            .map(item -> {
                String[] parts = item.split(":");
                return new OrderItem(
                    parts[0],
                    parts[0],  // Tên = id (giả lập)
                    Integer.parseInt(parts[1]),
                    Double.parseDouble(parts[2])
                );
            })
            .collect(Collectors.toList());
    }

    public void writeReport(String filePath, String content) throws IOException {
        Files.writeString(Paths.get(filePath), content);
    }
}
// ReportService.java
public class ReportService {

    public String generateDailyReport(OrderService orderService, LocalDate date) {
        StringBuilder sb = new StringBuilder();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");

        sb.append("=== BÁO CÁO NGÀY ").append(date.format(formatter)).append(" ===\n\n");

        List<Order> dailyOrders = orderService.findByDateRange(date, date);

        long totalOrders = dailyOrders.size();
        long completedOrders = dailyOrders.stream()
            .filter(o -> o.status() == OrderStatus.DELIVERED)
            .count();
        double revenue = dailyOrders.stream()
            .filter(o -> o.status() == OrderStatus.DELIVERED)
            .mapToDouble(Order::getTotal)
            .sum();

        sb.append("Tổng đơn hàng: ").append(totalOrders).append("\n");
        sb.append("Đơn hoàn thành: ").append(completedOrders).append("\n");
        sb.append("Doanh thu: ").append(String.format("%,.0f", revenue)).append(" VND\n");

        return sb.toString();
    }

    public String generateCustomerReport(OrderService orderService) {
        StringBuilder sb = new StringBuilder();
        sb.append("=== TOP 10 KHÁCH HÀNG ===\n\n");

        Map<String, Double> topCustomers = orderService.getTopCustomers(10);
        int rank = 1;
        for (Map.Entry<String, Double> entry : topCustomers.entrySet()) {
            sb.append(String.format("%d. %s - %,.0f VND%n",
                rank++, entry.getKey(), entry.getValue()));
        }

        return sb.toString();
    }
}

3. Main

public class Main {
    public static void main(String[] args) throws Exception {
        // Đọc dữ liệu
        FileService fileService = new FileService();
        List<Order> orders = fileService.readOrdersFromCsv("data/orders.csv");

        OrderService orderService = new OrderService(orders);
        ReportService reportService = new ReportService();

        // Menu
        Scanner sc = new Scanner(System.in);
        boolean running = true;

        while (running) {
            System.out.println("\n=== MENU ===");
            System.out.println("1. Xem tổng doanh thu");
            System.out.println("2. Xem doanh thu theo ngày");
            System.out.println("3. Xem top khách hàng");
            System.out.println("4. Xem sản phẩm bán chạy");
            System.out.println("5. Lọc đơn hàng theo trạng thái");
            System.out.println("6. Xuất báo cáo");
            System.out.println("0. Thoát");
            System.out.print("Chọn: ");

            int choice = sc.nextInt();
            sc.nextLine();

            switch (choice) {
                case 1 -> System.out.printf("Tổng DT: %,.0f VND%n",
                    orderService.getTotalRevenue());

                case 2 -> {
                    Map<LocalDate, Double> revenue = orderService.getRevenueByDate();
                    revenue.forEach((date, amount) ->
                        System.out.printf("%s: %,.0f VND%n", date, amount));
                }

                case 3 -> {
                    Map<String, Double> top = orderService.getTopCustomers(5);
                    top.forEach((id, amount) ->
                        System.out.printf("%s: %,.0f VND%n", id, amount));
                }

                case 4 -> {
                    Map<String, Long> best = orderService.getBestSellingProducts(5);
                    best.forEach((id, qty) ->
                        System.out.printf("%s: %d sản phẩm%n", id, qty));
                }

                case 5 -> {
                    System.out.print("Trạng thái (PENDING/CONFIRMED/SHIPPED/DELIVERED/CANCELLED): ");
                    String status = sc.nextLine();
                    List<Order> filtered = orderService.findByStatus(
                        OrderStatus.valueOf(status.toUpperCase())
                    );
                    System.out.println("Tìm thấy " + filtered.size() + " đơn");
                }

                case 6 -> {
                    String report = reportService.generateCustomerReport(orderService);
                    fileService.writeReport("output/report.txt", report);
                    System.out.println("Đã xuất báo cáo");
                }

                case 0 -> running = false;
            }
        }

        sc.close();
    }
}

YÊU CẦU NÂNG CAO

  1. Đa luồng: Xử lý nhiều file CSV song song với ExecutorService
  2. CompletableFuture: Chain các thao tác async
  3. Custom Exception: Tạo InvalidOrderException, FileProcessingException
  4. Generic Repository: Tạo Repository<T, ID> interface
  5. Date/Time: Lọc đơn hàng theo khoảng thời gian với LocalDate, LocalDateTime
  6. Optional: Tránh null trong toàn bộ code

TIÊU CHÍ ĐÁNH GIÁ

  • ✅ Code chạy đúng, không exception
  • ✅ Sử dụng Stream API hiệu quả
  • ✅ Áp dụng Generic, Optional
  • ✅ Xử lý lỗi đúng cách
  • ✅ Code sạch, dễ đọc
  • ✅ Có comment giải thích logic phức tạp

Hoàn thành Phase 2! Tiếp tục với Phase 3: Công cụ & Build tools


🧭 Điều hướng bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí