[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 24: API Chi tiết Đơn hàng & Bảo mật dữ liệu người dùng
Chào các bạn, mình đã trở lại!
Một sai lầm phổ biến của các bạn mới làm Backend là chỉ lọc đơn hàng theo id. Điều này dẫn đến lỗ hổng bảo mật nghiêm trọng: User A có thể đoán ID và xem trộm đơn hàng của User B. Trong bài này, mình sẽ hướng dẫn cách "Double Check" (Kiểm tra kép) để đảm bảo: Đúng người - Đúng đơn.
Chúng ta sẽ thực hiện gom dữ liệu từ 4 bảng: Orders, Order_Items, Products và Shipping_Addresses để trả về một bộ JSON đầy đủ nhất cho Frontend.
1. Tư duy kiến trúc: Gom dữ liệu từ nhiều nguồn
Để hiển thị một trang chi tiết đơn hàng hoàn chỉnh, chúng ta cần 2 nhóm dữ liệu:
Thông tin chung: Tổng tiền, trạng thái, ngày đặt, địa chỉ giao hàng (Lấy từ Orders + Shipping_Addresses).
Danh sách sản phẩm: Tên sản phẩm, giá tại thời điểm mua, số lượng (Lấy từ Order_Items + Products).
2. Tầng Model: Truy vấn đa tầng (Order.php)
Chúng ta sẽ chia nhỏ thành 2 hàm để code sạch sẽ và dễ bảo trì hơn.
File: app/Models/Order.php (Bổ sung)
<?php
namespace App\Models;
// ... (Các hàm cũ)
/**
* Tìm đơn hàng theo ID và phải thuộc về đúng UserID đó (Bảo mật)
*/
public function findById($id, $userId) {
$stmt = $this->db->prepare("
SELECT o.id, o.total_price, o.status, o.created_at,
sa.full_name, sa.address, sa.phone
FROM Orders o
LEFT JOIN Shipping_Addresses sa ON o.shipping_address_id = sa.id
WHERE o.id = ? AND o.user_id = ?
");
$stmt->execute([$id, $userId]);
return $stmt->fetch();
}
/**
* Lấy danh sách sản phẩm nằm trong đơn hàng
*/
public function getItems($orderId) {
$stmt = $this->db->prepare("
SELECT oi.product_id, p.name, oi.quantity, oi.price,
(oi.quantity * oi.price) AS total
FROM Order_Items oi
JOIN Products p ON oi.product_id = p.id
WHERE oi.order_id = ?
");
$stmt->execute([$orderId]);
return $stmt->fetchAll();
}
3. Tầng Controller: Kiểm soát và Phản hồi (OrderController.php)
Controller sẽ đóng vai trò người gác cổng. Nếu tìm không thấy đơn hàng (hoặc đơn hàng đó không phải của user đang đăng nhập), chúng ta trả về 404 ngay lập tức.
File: app/Controllers/OrderController.php (Bổ sung)
<?php
namespace App\Controllers;
use App\Core\Response;
use App\Models\Order;
use App\Middleware\AuthMiddleware;
class OrderController
{
/**
* API: Xem chi tiết một đơn hàng cụ thể
*/
public function show($id) {
// 1. Xác thực người dùng qua JWT
$user = AuthMiddleware::check();
$model = new Order();
// 2. Tìm thông tin đơn hàng (Kèm check user_id)
$order = $model->findById($id, $user->sub);
if (!$order) {
// Trả về 404 để hacker không biết đơn hàng có tồn tại hay không
Response::json(['error' => 'Không tìm thấy đơn hàng hoặc bạn không có quyền xem'], 404);
}
// 3. Lấy danh sách sản phẩm trong đơn
$items = $model->getItems($id);
// 4. Trả về kết quả tổng hợp
Response::json([
'status' => 'success',
'order' => $order,
'items' => $items
]);
}
}
4. Cấu hình Route Động (index.php)
Sử dụng Regex để bắt tham số ID từ URL.
File: public/index.php
use App\Controllers\OrderController;
$orderController = new OrderController();
// GET /api/orders/{id} - Xem chi tiết đơn hàng
if (preg_match('#^/api/orders/(\d+)$#', $uri, $matches) && $method === 'GET') {
$orderController->show($matches[1]);
}
5. Kiểm thử API (Test with Curl)
Hãy thử gọi đơn hàng mà bạn vừa tạo ở bài trước:
curl -X GET http://localhost:8000/api/orders/15 \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Output mong đợi:
{
"status": "success",
"order": {
"id": 15,
"total_price": "29990000.00",
"status": "pending",
"full_name": "Nguyễn Văn Hoàng",
"address": "Phú Nhuận, TP.HCM"
},
"items": [
{
"product_id": 3,
"name": "iPhone 15 Pro Max",
"quantity": 1,
"price": "29990000.00",
"total": "29990000.00"
}
]
}
Tạm kết
Vậy là bạn đã làm chủ được luồng dữ liệu phức tạp nhất trong một hệ thống bán hàng. Việc bảo mật tốt ở bước này sẽ giúp website của bạn tránh được những rủi ro pháp lý về lộ thông tin cá nhân.
Hãy để lại bình luận phía dưới nhé! Đừng quên Upvote để tiếp thêm động lực cho "bố đời". Chúc bạn code vui vẻ!
All rights reserved