diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php
index 4aad315..6a673d2 100644
--- a/app/Controllers/HomeController.php
+++ b/app/Controllers/HomeController.php
@@ -52,7 +52,7 @@ class HomeController extends Controller
'noindex' => $seo['noindex'],
'jsonld' => $faqJsonLd,
],
- 'banners' => array_filter($banner->all(), fn($b) => ($b['status'] ?? 1) == 1),
+ 'banners' => $banner->whereLimit('status', 1, 20),
'categories'=> $category->all(),
'products' => $product->featured(8),
'news' => $news->published(3),
diff --git a/app/Controllers/ProductController.php b/app/Controllers/ProductController.php
index 8dd5f3a..2a5c8d3 100644
--- a/app/Controllers/ProductController.php
+++ b/app/Controllers/ProductController.php
@@ -10,11 +10,15 @@ class ProductController extends Controller
{
$product = new Product();
$category = new Category();
+ $perPage = 12;
+ $page = max(1, (int)($_GET['page'] ?? 1));
$catId = isset($_GET['cat']) ? (int)$_GET['cat'] : 0;
- $products = $catId
- ? $product->byCategory($catId)
- : array_filter($product->all(), fn($p) => ($p['status'] ?? 1) == 1);
+ // V0.9.4:列表改为分页查询(WHERE status=1 + LIMIT/OFFSET + 总数),不再全表拉回。
+ $res = $product->listing($page, $perPage, $catId ?: null);
+ $products = $res['items'];
+ $total = $res['total'];
+ $totalPages = max(1, (int) ceil($total / $perPage));
$cat = $catId ? $category->find($catId) : null;
$catName = $cat['name'] ?? '';
@@ -56,6 +60,8 @@ class ProductController extends Controller
'categories'=> $category->all(),
'activeCat' => $catId,
'cat' => $cat,
+ 'page' => $page,
+ 'totalPages'=> $totalPages,
]);
}
diff --git a/app/Core/App.php b/app/Core/App.php
index d15f791..8a4fb0e 100644
--- a/app/Core/App.php
+++ b/app/Core/App.php
@@ -328,9 +328,17 @@ class App
return $v;
}
- /** 动态生成 Sitemap XML(Google/Bing/Baidu 自动抓取) */
+ /** 动态生成 Sitemap XML(Google/Bing/Baidu 自动抓取)。V0.9.4:加文件缓存(TTL),避免每次爬虫请求全表扫描。 */
private static function outputSitemap(): void
{
+ $cacheFile = rtrim(BASE_PATH, '/') . '/storage/cache/sitemap.xml';
+ $ttl = 3600;
+ if (is_file($cacheFile) && (time() - filemtime($cacheFile)) < $ttl) {
+ header('Content-Type: application/xml; charset=utf-8');
+ readfile($cacheFile);
+ exit;
+ }
+ ob_start();
header('Content-Type: application/xml; charset=utf-8');
echo '' . "\n";
echo '' . "\n";
@@ -402,6 +410,12 @@ class App
}
echo '';
+ $xml = ob_get_clean();
+ // 写文件缓存(best-effort:目录不存在则创建,写失败不影响本次输出)
+ $dir = dirname($cacheFile);
+ if (!is_dir($dir)) @mkdir($dir, 0755, true);
+ if (is_dir($dir)) @file_put_contents($cacheFile, $xml);
+ echo $xml;
exit;
}
}
diff --git a/app/Core/Helper.php b/app/Core/Helper.php
index 9e2437d..e6ec1ff 100644
--- a/app/Core/Helper.php
+++ b/app/Core/Helper.php
@@ -24,7 +24,14 @@ if (!function_exists('site_url')) {
}
function asset(string $path = ''): string
{
- return site_url('assets/' . ltrim($path, '/'));
+ $url = site_url('assets/' . ltrim($path, '/'));
+ // V0.9.4:追加文件 mtime 作为版本串,使 theme.css 等静态资源在「重新生成/部署」后自动击穿浏览器长缓存
+ //(nginx 对已改资源设了 expires 1y,无版本串会导致老访客最长 1 年看不到更新)。
+ $real = rtrim(BASE_PATH, '/') . '/public/assets/' . ltrim($path, '/');
+ if (is_file($real)) {
+ $url .= '?v=' . filemtime($real);
+ }
+ return $url;
}
function e($v): string
{
diff --git a/app/Core/Model.php b/app/Core/Model.php
index 95ff34f..2c1b248 100644
--- a/app/Core/Model.php
+++ b/app/Core/Model.php
@@ -193,7 +193,7 @@ class Model
* @param string|null $col 过滤列(null 表示全表计数)
* @param mixed $val 过滤值
*/
- public function count(?string $col = null, $val = null): int
+ public function count(?string $col = null, mixed $val = null): int
{
if (Db::driver() === 'mysql') {
if ($col === null) {
diff --git a/app/Models/Product.php b/app/Models/Product.php
index 0dc34d1..dd80f14 100644
--- a/app/Models/Product.php
+++ b/app/Models/Product.php
@@ -18,6 +18,34 @@ class Product extends Model
// 不再 SELECT * 全表拉回再 array_filter(原写法每次请求都全表扫描)。
return $this->whereLimit('status', 1, $limit);
}
+ /**
+ * V0.9.4:产品列表分页。仅取 status=1 的活跃商品,按 $orderBy 排序,带 LIMIT/OFFSET;
+ * 同时返回总数用于翻页。避免每次列表请求都 SELECT * 全表拉回再 array_filter(原写法随 SKU 增长变重)。
+ * 文件模式回退为全表过滤 + array_slice。
+ * @return array{items:array,total:int}
+ */
+ public function listing(int $page, int $perPage, ?int $catId = null): array
+ {
+ $page = max(1, $page);
+ $perPage = max(1, $perPage);
+ $offset = ($page - 1) * $perPage;
+ if (\Core\Db::driver() === 'mysql') {
+ $where = 'WHERE status = 1';
+ $args = [];
+ if ($catId !== null) { $where .= ' AND category_id = ?'; $args[] = $catId; }
+ $items = \Core\Db::query(
+ "SELECT * FROM `{$this->table}` {$where} ORDER BY `{$this->orderBy}` ASC LIMIT ? OFFSET ?",
+ array_merge($args, [$perPage, $offset])
+ )->fetchAll();
+ $total = (int) \Core\Db::query("SELECT COUNT(*) FROM `{$this->table}` {$where}", $args)->fetchColumn();
+ return ['items' => $items, 'total' => $total];
+ }
+ // 文件模式兜底
+ $all = array_filter($this->all(), fn($p) => ($p['status'] ?? 1) == 1);
+ if ($catId !== null) $all = array_filter($all, fn($p) => ($p['category_id'] ?? 0) == $catId);
+ $all = array_values($all);
+ return ['items' => array_slice($all, $offset, $perPage), 'total' => count($all)];
+ }
public function specsArray($p): array
{
$s = $p['specs'] ?? '';
diff --git a/app/Views/product/index.php b/app/Views/product/index.php
index 971a1cb..56d35fb 100644
--- a/app/Views/product/index.php
+++ b/app/Views/product/index.php
@@ -44,5 +44,18 @@ $sub = $cat ? $cat['description'] : '水冷循环 / 相变蓄冷 / 涡扇风冷
+ 1): ?>
+ site_url($base . ($activeCat ? '&' : '?') . 'page=' . $p);
+ ?>
+
+
diff --git a/coolcoth.com.conf b/coolcoth.com.conf
index e238218..a376b69 100644
--- a/coolcoth.com.conf
+++ b/coolcoth.com.conf
@@ -24,6 +24,14 @@ server {
root /www/wwwroot/coolcoth.com/public; # 运行目录 = /public
index index.php index.html;
+ # ── Gzip 压缩(V0.9.4:文本资源压缩,降低传输体积)──
+ gzip on;
+ gzip_vary on;
+ gzip_min_length 1024;
+ gzip_proxied any;
+ gzip_comp_level 5;
+ gzip_types text/plain text/css text/xml application/xml application/javascript application/json application/x-javascript image/svg+xml;
+
# ── SSL 证书(宝塔申请 Let's Encrypt 后自动填充,或手动指定)──
# ssl_certificate /www/server/panel/vhost/cert/coolcoth.com/fullchain.pem;
# ssl_certificate_key /www/server/panel/vhost/cert/coolcoth.com/privkey.pem;