봇 요청 스레드의 동기 생성을 제거하고 디스크 스트리밍 서빙으로 전환했다. 신선도가 만료되면 생성 잡만 예약하고 기존 세트(stale) 또는 503+Retry-After 로 응답해, 대용량에서 봇 요청이 OOM/타임아웃을 유발하던 경로를 끊었다. - 잡: ShouldBeUnique(고정 uniqueId)+uniqueFor+retryUntil, timeout 300→1800, backoff - 컨트롤러: 인덱스 stale/503 분기 + 자식 라우트(.xml/.xml.gz 동일 액션) - 스케줄 withoutOverlapping, 관리자 재생성 큐 이관 - 설정 4키(분할 기준·압축·stale 서빙·수집기 상한) + 고급탭 Sitemap 캐시 짝 - SitemapManager 응답 메시지 다국어화 설정 저장 테스트가 실제 설정 파일(사이트명/언어 포함)을 덮어쓰던 사전결함을 TestCase 전역 격리로 차단하고, 두 탭이 같은 다국어 키를 공유해 한쪽에 틀린 한도를 안내하던 사전결함을 키 분리로 해소했다.
61 lines
2.7 KiB
PHP
61 lines
2.7 KiB
PHP
<?php
|
|
|
|
use App\Http\Controllers\Api\Public\SitemapController;
|
|
use App\Seo\TemplateRouteResolver;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Route;
|
|
|
|
// 개발용 라우트 - 디버그 모드 + 관리자 인증 필수
|
|
Route::get('/dev', function () {
|
|
// 1. 디버그 모드 확인
|
|
if (! config('app.debug')) {
|
|
abort(404);
|
|
}
|
|
|
|
// 2. 관리자 인증 확인 (세션 기반 - stateful 미들웨어로 로그인 시 세션 생성)
|
|
$user = Auth::guard('web')->user();
|
|
if (! $user || ! $user->is_super) {
|
|
abort(403, '관리자 권한이 필요합니다.');
|
|
}
|
|
|
|
return view('dev-dashboard');
|
|
})->name('web.dev');
|
|
|
|
// Admin 라우트 - admin 템플릿 의존성 검증
|
|
Route::prefix('admin')
|
|
->middleware('template.dependencies:admin')
|
|
->group(function () {
|
|
Route::get('/{any?}', function (Request $request) {
|
|
// 미등록 경로는 SPA 셸 본문 + HTTP 404 (soft 404 방지 — 공개#47).
|
|
// 본문은 그대로라 클라이언트가 404 레이아웃을 렌더하고, 봇은 404 색인 제외.
|
|
$path = '/'.ltrim($request->getPathInfo(), '/');
|
|
if (! app(TemplateRouteResolver::class)->routeExists($path, 'admin')) {
|
|
return response(view('admin'), 404);
|
|
}
|
|
|
|
return view('admin');
|
|
})->where('any', '(?!.*\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)).*');
|
|
});
|
|
|
|
// Sitemap XML 라우트
|
|
// 자식 경로는 SitemapFileStore::childUrl() 이 sitemapindex 의 <loc> 에 기록하는 값과 일치해야 한다.
|
|
// gzip 여부는 manifest 가 결정하므로 .xml / .xml.gz 를 같은 액션에서 처리한다.
|
|
Route::get('/sitemap.xml', [SitemapController::class, 'index'])->name('web.sitemap');
|
|
Route::get('/sitemap-{n}.xml', [SitemapController::class, 'child'])->whereNumber('n')->name('web.sitemap.child');
|
|
Route::get('/sitemap-{n}.xml.gz', [SitemapController::class, 'child'])->whereNumber('n')->name('web.sitemap.child.gz');
|
|
|
|
// User 라우트 - user 템플릿 의존성 검증 + SEO 봇 감지
|
|
Route::middleware(['template.dependencies:user', 'seo'])
|
|
->group(function () {
|
|
Route::get('/{any?}', function (Request $request) {
|
|
// 미등록 경로는 SPA 셸 본문 + HTTP 404 (soft 404 방지 — 공개#47).
|
|
$path = '/'.ltrim($request->getPathInfo(), '/');
|
|
if (! app(TemplateRouteResolver::class)->routeExists($path, 'user')) {
|
|
return response(view('app'), 404);
|
|
}
|
|
|
|
return view('app');
|
|
})->where('any', '(?!admin)(?!api)(?!plugins)(?!.*\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot)).*');
|
|
});
|