Tạo QR Code API chuẩn Enterprise: Đừng bắt Server "vẽ" lại QR mỗi lần user F5!
Trong môi trường Enterprise (nhất là khi code API cho Mobile App hoặc SPA React/Vue), chúng ta phải đối mặt với 3 bài toán:
- Format: API không thể trả về một cái View HTML. Nó phải trả về
Base64 String(đối với ảnh PNG) hoặc chuỗiSVG. - Tùy biến (Customization): QR Code phải có Logo công ty ở giữa, màu sắc nhận diện thương hiệu.
- Hiệu năng (Performance): Thuật toán sinh QR ngốn khá nhiều CPU. Nếu là mã QR tĩnh (VD: Link profile của user), ta không được phép sinh lại nhiều lần.
Hôm nay, mình sẽ lên bài Viblo giải quyết trọn vẹn 3 bài toán này bằng Service Pattern và Cache.
Lời mở đầu: Sự ngây ngô khi sinh QR Code
Tạo QR code rất dễ. Nhưng bạn có biết, mỗi lần gọi hàm QrCode::generate(), server phải chạy thuật toán mã hóa Reed-Solomon, tính toán ma trận điểm ảnh, và render ra một file ảnh?
Nếu bạn làm chức năng "QR Code Profile" cho 1 user, và user đó đăng cái QR lên Facebook. 10.000 người quét mã đó cùng lúc. Nếu bạn viết code sinh QR trực tiếp trong Controller, CPU của server sẽ phải gồng gánh việc "vẽ" lại cái QR đó 10.000 lần cho cùng một nội dung. Server của bạn sẽ bốc khói!
Hôm nay, chúng ta sẽ dùng simplesoftwareio/simple-qrcode để thiết kế một API sinh QR Code trả về Base64, chèn Logo thương hiệu, và áp dụng Cache để tối ưu CPU.
Bước 1: Cài đặt và cấu hình "vũ khí"
Đầu tiên, kéo thư viện về dự án của bạn:
composer require simplesoftwareio/simple-qrcode
LƯU Ý HẠNG NẶNG (Dành cho dân server):
Mặc định thư viện này sinh ra mã SVG (định dạng vector). Nếu bạn muốn sinh ra ảnh PNG (để chèn Logo và Mobile App dễ hiển thị hơn), server của bạn BẮT BUỘC phải cài extension ext-gd hoặc ext-imagick của PHP. Đừng quên check cái này khi deploy lên Production nhé!
Bước 2: Thiết kế QrCodeService - Bộ não xử lý
Chúng ta không viết logic tạo QR vào Controller. Hãy đẩy nó vào một Service.
Tại sao phải dùng Error Correction Level H (Mức H)? Khi bạn muốn nhét 1 cái Logo vào giữa QR Code, thực chất bạn đang che khuất (làm hỏng) một phần dữ liệu của mã QR. Mức H (High) giúp QR Code có thể phục hồi lên đến 30% dữ liệu bị mất, đảm bảo điện thoại vẫn quét được dù có Logo to chà bá ở giữa.
php artisan make:service QrCodeService
// app/Services/QrCodeService.php
namespace App\Services;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class QrCodeService
{
/**
* Sinh mã QR Tĩnh (Static) - Có áp dụng Cache
* Ví dụ: Link Profile cá nhân, Link tải App
*/
public function generateStaticQr(string $identifier, string $data): string
{
$cacheKey = 'static_qr_' . $identifier;
// Lưu Cache vĩnh viễn, vì data tĩnh không thay đổi
return Cache::rememberForever($cacheKey, function () use ($data) {
return $this->buildQrBase64($data);
});
}
/**
* Sinh mã QR Động (Dynamic) - Có TTL (Time To Live)
* Ví dụ: Mã thanh toán đơn hàng, Mã check-in OTP sống trong 15 phút
*/
public function generateDynamicQr(string $orderId, string $data): string
{
$cacheKey = 'dynamic_qr_' . $orderId;
return Cache::remember($cacheKey, now()->addMinutes(15), function () use ($data) {
return $this->buildQrBase64($data);
});
}
/**
* Hàm Lõi: Cấu hình và Render QR Code ra chuỗi Base64
*/
private function buildQrBase64(string $data): string
{
try {
$qrImage = QrCode::format('png')
->size(300) // Kích thước 300x300 pixel
->margin(1) // Viền trắng mỏng
->errorCorrection('H') // Mức sửa lỗi cao nhất để chèn Logo
->color(0, 0, 0) // Màu QR (Đen)
// ->backgroundColor(255, 255, 255) // Màu nền (Trắng)
// Chèn logo (Logo phải nằm ở public/images/logo.png)
// Tham số 0.3 nghĩa là logo chiếm 30% diện tích
->merge(public_path('images/logo.png'), 0.3, true)
->generate($data);
// Chuyển binary raw thành Base64 chuẩn để nhét vào JSON
return 'data:image/png;base64,' . base64_encode($qrImage);
} catch (\Exception $e) {
Log::error("Lỗi sinh QR Code: " . $e->getMessage());
throw new \Exception("Không thể tạo mã QR vào lúc này.");
}
}
}
Lưu ý: Nếu bạn test, hãy kiếm một file ảnh logo.png nhỏ gọn nhét vào thư mục public/images/ nhé.
Bước 3: Controller điều hướng
Controller giờ đây chỉ nhận Request và gọi Service. Gọn gàng và thanh lịch.
php artisan make:controller Api/QrController
// app/Http/Controllers/Api/QrController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\QrCodeService;
use Illuminate\Http\Request;
class QrController extends Controller
{
protected QrCodeService $qrService;
public function __construct(QrCodeService $qrService)
{
$this->qrService = $qrService;
}
/**
* API Lấy mã QR chia sẻ Profile của User
*/
public function getUserProfileQr(Request $request)
{
$user = $request->user(); // Lấy user đang đăng nhập
$profileUrl = "[https://yourdomain.com/user/](https://yourdomain.com/user/)" . $user->username;
// Gọi hàm sinh QR tĩnh (Có Cache vĩnh viễn)
$qrBase64 = $this->qrService->generateStaticQr($user->username, $profileUrl);
return response()->json([
'success' => true,
'message' => 'Lấy QR Code thành công',
'data' => [
'username' => $user->username,
'qr_image' => $qrBase64
]
]);
}
/**
* API Lấy mã QR thanh toán Đơn hàng (Động)
*/
public function getPaymentQr(Request $request, $orderId)
{
// ... Logic kiểm tra orderId có tồn tại không ...
$paymentData = "PAY|ORD-{$orderId}|AMOUNT-500000";
// Gọi hàm sinh QR động (Sống 15 phút)
$qrBase64 = $this->qrService->generateDynamicQr($orderId, $paymentData);
return response()->json([
'success' => true,
'message' => 'Lấy QR Code thanh toán thành công',
'data' => [
'order_id' => $orderId,
'qr_image' => $qrBase64,
'expires_in' => '15 minutes'
]
]);
}
}
Đăng ký Route:
Route::middleware('auth:sanctum')->group(function () {
Route::get('/qr/profile', [QrController::class, 'getUserProfileQr']);
Route::get('/qr/payment/{orderId}', [QrController::class, 'getPaymentQr']);
});
Bước 4: Thử lửa với Postman
Khởi động server và gọi thử API lấy QR Profile.
- Method: GET
- URL: http://127.0.0.1:8000/api/qr/profile
- Headers:
Authorization:Bearer {token_cua_ban}Accept:application/json
Kết quả (Response):
{
"success": true,
"message": "Lấy QR Code thành công",
"data": {
"username": "kysudom",
"qr_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5... (chuỗi Base64 rất dài)"
}
}
Mẹo cho Frontend/Mobile:
Bên Frontend (React, Vue, iOS, Android) khi nhận được chuỗi qr_image này, họ KHÔNG CẦN phải tải file từ server. Họ chỉ việc quăng thẳng nó vào thẻ <img> là xong:
<img src="data:image/png;base64,iVBORw0KGgo..." alt="QR Code" />
Sự kỳ diệu của Cache (Zero CPU Cost):
Hãy bấm nút Send trên Postman thêm 5 lần nữa. Bạn sẽ thấy thời gian phản hồi (Response Time) giảm xuống chỉ còn 5ms - 10ms. Vì lúc này, server không còn "vẽ" lại QR Code nữa, nó chỉ móc cái chuỗi Base64 dài ngoằng kia từ RAM (Redis/Cache) ra và trả về cho bạn. Tối ưu sức mạnh tuyệt đối!
Tóm lại
Một chiếc mã QR vuông vức bé nhỏ, nhưng để tích hợp nó vào hệ thống Enterprise một cách chuyên nghiệp, chúng ta cần:
- Trả về
Base64để Frontend hiển thị siêu tốc, giảm tải I/O (Đọc/Ghi file) cho server. - Nâng mức
Error Correction (H)để chèn Logo thương hiệu không bị vỡ. - Dùng
Cacheđể chặn đứng các tác vụ ngốn CPU lặp đi lặp lại.
Kiến trúc vững từ những chi tiết nhỏ nhất mới là thứ định hình nên một Kỹ sư giỏi. Chúc anh em đem vào dự án thành công!
All Rights Reserved