perf: cache résultats de recherche par requête, invalidé sur create/update/delete
This commit is contained in:
+1
-1
@@ -8,6 +8,6 @@ vendor/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Fichiers uploadés et cache (propriété www-data)
|
# Fichiers uploadés et cache générés (propriété www-data)
|
||||||
data/*/files/
|
data/*/files/
|
||||||
data/_cache/
|
data/_cache/
|
||||||
|
|||||||
@@ -6,10 +6,15 @@
|
|||||||
"published": true,
|
"published": true,
|
||||||
"published_at": "2026-06-08 07:00",
|
"published_at": "2026-06-08 07:00",
|
||||||
"created_at": "2026-05-12 23:01:34",
|
"created_at": "2026-05-12 23:01:34",
|
||||||
"updated_at": "2026-05-12 23:18:32",
|
"updated_at": "2026-05-12 23:29:17",
|
||||||
"revisions": [],
|
"revisions": [],
|
||||||
"cover": "",
|
"cover": "cover.jpg",
|
||||||
"files_meta": [],
|
"files_meta": {
|
||||||
|
"cover.jpg": {
|
||||||
|
"author": "",
|
||||||
|
"source_url": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"external_links": [
|
"external_links": [
|
||||||
{
|
{
|
||||||
"url": "https://varlog.a5l.fr/post/post-install",
|
"url": "https://varlog.a5l.fr/post/post-install",
|
||||||
@@ -24,6 +29,6 @@
|
|||||||
],
|
],
|
||||||
"seo_title": "",
|
"seo_title": "",
|
||||||
"seo_description": "",
|
"seo_description": "",
|
||||||
"og_image": "",
|
"og_image": "https://varlog.a5l.fr/file?uuid=0bba1ad7-e4cb-49a6-9467-fcfac2e09a93&name=cover.jpg",
|
||||||
"category": "informatique"
|
"category": "informatique"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,19 @@
|
|||||||
"title": "Premiers pas DevOps : préparer un système Debian fraîchement installé"
|
"title": "Premiers pas DevOps : préparer un système Debian fraîchement installé"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"cover": "",
|
"cover": "cover.jpg",
|
||||||
"files_meta": [],
|
"files_meta": {
|
||||||
|
"cover.jpg": {
|
||||||
|
"author": "",
|
||||||
|
"source_url": "https://3.bp.blogspot.com/-WER6d6fmIXU/WD1K9pOVoQI/AAAAAAAAGt4/47YWFQ7r7HQs2HTlkoz9KRt-1SmBXXaWwCLcB/s320/debian-logo.jpg",
|
||||||
|
"title": "Logo Debian GNU Linux",
|
||||||
|
"meta": {
|
||||||
|
"mime": "image/jpeg",
|
||||||
|
"size": 12823,
|
||||||
|
"width": 320
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"external_links": [],
|
"external_links": [],
|
||||||
"seo_title": "",
|
"seo_title": "",
|
||||||
"seo_description": "",
|
"seo_description": "",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+26
-18
@@ -1851,27 +1851,35 @@ switch ($action) {
|
|||||||
|
|
||||||
case 'search':
|
case 'search':
|
||||||
require_once BASE_PATH . '/src/SearchEngine.php';
|
require_once BASE_PATH . '/src/SearchEngine.php';
|
||||||
$searchQuery = trim($_GET['q'] ?? '');
|
$searchQuery = trim($_GET['q'] ?? '');
|
||||||
$searchResults = [];
|
$searchResults = [];
|
||||||
if ($searchQuery !== '') {
|
if ($searchQuery !== '') {
|
||||||
$privateCats = $articles->getPrivateCategories();
|
$isAnonSearch = !isLoggedIn();
|
||||||
// Utilise l'index pré-construit si disponible (lecture d'un seul fichier JSON)
|
// Lecture du cache pour les visiteurs anonymes
|
||||||
// Sinon fallback sur getAll() qui scanne tous les répertoires
|
if ($isAnonSearch) {
|
||||||
$rawPool = $articles->getSearchIndex() ?? $articles->getAll(true);
|
$searchResults = $articles->getSearchCache($searchQuery) ?? [];
|
||||||
$searchPool = array_values(array_filter($rawPool, static function (array $a) use ($privateCats): bool {
|
}
|
||||||
if (!($a['published'] ?? false)) {
|
if (empty($searchResults)) {
|
||||||
return false;
|
$privateCats = $articles->getPrivateCategories();
|
||||||
|
$rawPool = $articles->getSearchIndex() ?? $articles->getAll(true);
|
||||||
|
$searchPool = array_values(array_filter($rawPool, static function (array $a) use ($privateCats): bool {
|
||||||
|
if (!($a['published'] ?? false)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$cat = trim($a['category'] ?? '');
|
||||||
|
if ($cat !== '' && in_array($cat, $privateCats, true) && !isLoggedIn()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (strtotime((string)($a['published_at'] ?? '')) > time() && !hasCapability('view_previews')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
$searchResults = (new SearchEngine())->search($searchQuery, $searchPool);
|
||||||
|
if ($isAnonSearch && !empty($searchResults)) {
|
||||||
|
$articles->setSearchCache($searchQuery, $searchResults);
|
||||||
}
|
}
|
||||||
$cat = trim($a['category'] ?? '');
|
}
|
||||||
if ($cat !== '' && in_array($cat, $privateCats, true) && !isLoggedIn()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (strtotime((string)($a['published_at'] ?? '')) > time() && !hasCapability('view_previews')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}));
|
|
||||||
$searchResults = (new SearchEngine())->search($searchQuery, $searchPool);
|
|
||||||
}
|
}
|
||||||
include BASE_PATH . '/templates/search.php';
|
include BASE_PATH . '/templates/search.php';
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -649,6 +649,53 @@ class ArticleManager
|
|||||||
$this->dataDir . '/search_index.json',
|
$this->dataDir . '/search_index.json',
|
||||||
json_encode($index, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
json_encode($index, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||||
);
|
);
|
||||||
|
$this->clearSearchResultsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Vide le cache des résultats de recherche (appelé après chaque modification). */
|
||||||
|
public function clearSearchResultsCache(): void
|
||||||
|
{
|
||||||
|
$dir = $this->dataDir . '/_cache/search';
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (scandir($dir) as $f) {
|
||||||
|
if (str_ends_with($f, '.json')) {
|
||||||
|
@unlink($dir . '/' . $f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne les résultats mis en cache pour une requête anonyme, ou null si absent.
|
||||||
|
* @return array<array>|null
|
||||||
|
*/
|
||||||
|
public function getSearchCache(string $query): ?array
|
||||||
|
{
|
||||||
|
$path = $this->searchCachePath($query);
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$data = json_decode((string) file_get_contents($path), true);
|
||||||
|
return is_array($data) ? $data : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Persiste les résultats d'une requête anonyme dans le cache. */
|
||||||
|
public function setSearchCache(string $query, array $results): void
|
||||||
|
{
|
||||||
|
$dir = $this->dataDir . '/_cache/search';
|
||||||
|
if (!is_dir($dir)) {
|
||||||
|
mkdir($dir, 0755, true);
|
||||||
|
}
|
||||||
|
file_put_contents(
|
||||||
|
$this->searchCachePath($query),
|
||||||
|
json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function searchCachePath(string $query): string
|
||||||
|
{
|
||||||
|
return $this->dataDir . '/_cache/search/' . md5(mb_strtolower(trim($query))) . '.json';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Retourne l'index pré-construit, ou null s'il n'existe pas encore. */
|
/** Retourne l'index pré-construit, ou null s'il n'existe pas encore. */
|
||||||
|
|||||||
Reference in New Issue
Block a user