perf: V0.9.4 商品列表分页 + sitemap 文件缓存 + banner/asset/gzip/类型修复
- P1: Product::listing() 分页(WHERE status=1+LIMIT/OFFSET+总数),ProductController 改用并加翻页器,消除列表全表
- P1: outputSitemap() 加 storage/cache/sitemap.xml 文件缓存(TTL 3600s),消除每次爬虫全表
- P2: 首页 banner 改 whereLimit('status',1,20)
- P2: asset() 追加 ?v=filemtime 版本串,theme.css 改后自动击穿 1y 浏览器缓存
- P2: nginx 启用 gzip(文本资源压缩)
- P2: Model::count() $val 隐式可空改 mixed(PHP 8.4+ 弃用)
This commit is contained in:
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
+15
-1
@@ -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 '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
||||
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||
@@ -402,6 +410,12 @@ class App
|
||||
}
|
||||
|
||||
echo '</urlset>';
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -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
|
||||
{
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
@@ -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'] ?? '';
|
||||
|
||||
@@ -44,5 +44,18 @@ $sub = $cat ? $cat['description'] : '水冷循环 / 相变蓄冷 / 涡扇风冷
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php if (($totalPages ?? 1) > 1): ?>
|
||||
<?php
|
||||
$base = 'products' . ($activeCat ? '?cat=' . $activeCat : '');
|
||||
$mk = fn($p) => site_url($base . ($activeCat ? '&' : '?') . 'page=' . $p);
|
||||
?>
|
||||
<nav class="pager" style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:34px 0">
|
||||
<?php if ($page > 1): ?><a class="btn btn-ghost" href="<?php echo $mk($page - 1); ?>">上一页</a><?php endif; ?>
|
||||
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
|
||||
<a class="btn btn-ghost" style="<?php echo $i == $page ? 'background:var(--c-primary);color:#fff' : '' ?>" href="<?php echo $mk($i); ?>"><?php echo $i; ?></a>
|
||||
<?php endfor; ?>
|
||||
<?php if ($page < $totalPages): ?><a class="btn btn-ghost" href="<?php echo $mk($page + 1); ?>">下一页</a><?php endif; ?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user