feat(seo): 사이트맵 증분 저장소 + 리소스 리스너 재배선
사이트맵을 리소스 단위로 영속화(sitemap_urls)해 진정한 증분 갱신과 도메인 재쿼리 없는 파일 재작성을 구현했다. 리소스 공개/비공개/삭제 시 해당 항목만 색인이 갱신되고, 관리자=full·스케줄러=auto·리소스 변경=증분으로 동작한다. - sitemap_urls 마이그레이션 + SitemapUrl 모델. loc(2048) 를 unique 에 직접 넣으면 InnoDB 키 길이를 초과하므로 loc_hash(sha256) 컬럼을 identity 로 사용 - SitemapUrlRepository(멱등 upsert / remove / 스트리밍) + SitemapIndexer (+ 제3자용 sitemap.index.collect_for_resource 필터 훅) - 4 리스너 재배선: 공개→색인 / 비공개·삭제→색인 해제 + 재생성 잡 디바운스 디스패치. 3 기여자가 resource_type/resource_id 를 함께 emit 해 전체·증분 identity 를 일치 - 재생성 모드를 SitemapGenerationMode Backed Enum 으로 통일(Command→Job→Manager) - seo:generate-sitemap 에 --rebuild / --mode 옵션 추가
This commit is contained in:
1 parent
1a6fca0d6b
commit
1629684156
34 files changed
+2027
-23
No files matched your search
@@ -4,12 +4,14 @@ namespace Tests\Unit\Seo;
|
||||
|
||||
use App\Contracts\Extension\CacheInterface;
|
||||
use App\Contracts\Repositories\ConfigRepositoryInterface;
|
||||
use App\Contracts\Repositories\SitemapUrlRepositoryInterface;
|
||||
use App\Extension\HookManager;
|
||||
use App\Extension\Storage\CoreStorageDriver;
|
||||
use App\Seo\Contracts\SitemapContributorInterface;
|
||||
use App\Seo\SitemapFileStore;
|
||||
use App\Seo\SitemapGenerator;
|
||||
use App\Seo\SitemapManager;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Mockery;
|
||||
@@ -31,6 +33,8 @@ use Tests\TestCase;
|
||||
*/
|
||||
class SitemapManagerTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
/**
|
||||
* 이 테스트가 등록·검증하는 sitemap 훅 목록
|
||||
*/
|
||||
@@ -128,6 +132,7 @@ class SitemapManagerTest extends TestCase
|
||||
$cache ?? Mockery::spy(CacheInterface::class),
|
||||
$config ?? Mockery::spy(ConfigRepositoryInterface::class),
|
||||
$this->storage,
|
||||
$this->app->make(SitemapUrlRepositoryInterface::class),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -299,13 +304,14 @@ class SitemapManagerTest extends TestCase
|
||||
|
||||
// 생성 도중 예외를 던지는 generator 로 실패를 강제한다.
|
||||
$generator = Mockery::mock(SitemapGenerator::class);
|
||||
$generator->shouldReceive('generateToWriter')->andThrow(new \RuntimeException('디스크 장애'));
|
||||
$generator->shouldReceive('generateToWriterFromEntries')->andThrow(new \RuntimeException('디스크 장애'));
|
||||
|
||||
$manager = new SitemapManager(
|
||||
$generator,
|
||||
Mockery::spy(CacheInterface::class),
|
||||
Mockery::spy(ConfigRepositoryInterface::class),
|
||||
$this->storage,
|
||||
$this->incrementalRepository(),
|
||||
);
|
||||
|
||||
$result = $manager->regenerate();
|
||||
@@ -329,18 +335,36 @@ class SitemapManagerTest extends TestCase
|
||||
$cache->shouldNotReceive('forget');
|
||||
|
||||
$generator = Mockery::mock(SitemapGenerator::class);
|
||||
$generator->shouldReceive('generateToWriter')->andThrow(new \RuntimeException('장애'));
|
||||
$generator->shouldReceive('generateToWriterFromEntries')->andThrow(new \RuntimeException('장애'));
|
||||
|
||||
$manager = new SitemapManager(
|
||||
$generator,
|
||||
$cache,
|
||||
Mockery::spy(ConfigRepositoryInterface::class),
|
||||
$this->storage,
|
||||
$this->incrementalRepository(),
|
||||
);
|
||||
|
||||
$this->assertFalse($manager->regenerate()['success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* incremental 모드로 진입시키는(테이블이 비어있지 않은) mock 저장소를 만듭니다.
|
||||
*
|
||||
* countVisible()>0 → auto 가 incremental 로 해석되어 rebuildStore(전체 재적재)를 건너뛰고
|
||||
* 곧바로 generateToWriterFromEntries 로 진입합니다(실패 경로 단순화).
|
||||
*
|
||||
* @return SitemapUrlRepositoryInterface mock 저장소
|
||||
*/
|
||||
private function incrementalRepository(): SitemapUrlRepositoryInterface
|
||||
{
|
||||
$repository = Mockery::mock(SitemapUrlRepositoryInterface::class);
|
||||
$repository->shouldReceive('countVisible')->andReturn(1);
|
||||
$repository->shouldReceive('streamVisible')->andReturn([]);
|
||||
|
||||
return $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* regenerate: seo.sitemap_urls_per_file 설정이 자식 분할에 반영된다
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user