CodeIgniter 4 website:
- Catalog 999 SGK (lớp 1-12) từ API, lọc theo lớp/môn/NXB, tìm kiếm
- Trang chủ grouped browse (bậc học -> lớp -> môn, ưu tiên môn quan trọng)
- Chi tiết sách: metadata đầy đủ, cover nghệ thuật /thumb/{W}x{H}/
- Đọc online qua gate captcha + countdown, PDF viewer pdf.js
- Download: captcha + token một lần + auto cache-bust version
- Tự đồng bộ catalog/cover khi API thay đổi (refresh:catalog)
- Legal: DMCA, miễn trừ trách nhiệm, điều khoản, bảo mật, liên hệ
70 lines
2.0 KiB
PHP
70 lines
2.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Libraries\BookApi;
|
|
use App\Libraries\CoverArt;
|
|
|
|
/**
|
|
* Thumbnail endpoint: /thumb/{W}x{H}/{id}.jpg
|
|
* Lazily generates art-directed thumbnails from the cached original cover.
|
|
*/
|
|
class Thumb extends BaseController
|
|
{
|
|
private BookApi $api;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->api = new BookApi();
|
|
}
|
|
|
|
public function serve(string $size, string $file)
|
|
{
|
|
// parse size + id
|
|
if (! preg_match('/^(\d{2,4})x(\d{2,4})$/', $size, $m)) {
|
|
return $this->response->setStatusCode(400);
|
|
}
|
|
[, $w, $h] = $m;
|
|
$id = preg_replace('/\.jpg$/', '', $file);
|
|
if (! $id || ! preg_match('/^[a-zA-Z0-9._-]+$/', $id)) {
|
|
return $this->response->setStatusCode(400);
|
|
}
|
|
|
|
$book = $this->api->book($id);
|
|
if (! $book || ! $book['coverEndpoint']) {
|
|
return $this->response->setStatusCode(404);
|
|
}
|
|
|
|
// ensure the original is cached locally (fetch on demand)
|
|
$dir = WRITEPATH . 'cache/covers';
|
|
$orig = $dir . '/' . preg_replace('/[^a-zA-Z0-9_-]/', '_', $id) . '.jpg';
|
|
if (! is_file($orig)) {
|
|
$tmp = $orig . '.tmp' . getmypid();
|
|
if ($this->api->fetchToFile($book['coverEndpoint'], $tmp)) {
|
|
if (! is_dir($dir)) {
|
|
mkdir($dir, 0775, true);
|
|
}
|
|
rename($tmp, $orig);
|
|
} else {
|
|
@unlink($tmp);
|
|
}
|
|
}
|
|
if (! is_file($orig)) {
|
|
return $this->response->setStatusCode(404);
|
|
}
|
|
|
|
$thumb = CoverArt::thumb($id, $orig, $book, (int) $w, (int) $h);
|
|
if (! $thumb || ! is_file($thumb)) {
|
|
return $this->response->setStatusCode(404);
|
|
}
|
|
|
|
header('Content-Type: image/jpeg');
|
|
header('Content-Length: ' . (string) filesize($thumb));
|
|
header('Cache-Control: public, max-age=604800');
|
|
readfile($thumb);
|
|
exit;
|
|
}
|
|
}
|