build(); }); return response($xml, 200)->header('Content-Type', 'application/xml; charset=utf-8'); } private function build(): string { // 레이아웃의 canonical(url()->current())과 같은 방식으로 요청 기준 주소를 쓴다. $baseUrl = rtrim(url('/'), '/'); $today = date('Y-m-d'); $xml = '' . PHP_EOL; $xml .= '' . PHP_EOL; // 고정 페이지. 로그인/회원가입/비밀번호 재설정은 색인 가치가 없어 제외한다. $xml .= $this->url("{$baseUrl}/", $today, 'daily', '1.0'); foreach (['rank', 'search', 'trailer', 'review', 'company'] as $section) { $xml .= $this->url("{$baseUrl}/movie/{$section}", $today, 'daily', '0.8'); } $xml .= $this->url("{$baseUrl}/tag", $today, 'daily', '0.6'); // 게시판 목록 $boards = DB::table('tb_board') ->where('is_display', '=', 1) ->orderBy('sort') ->pluck('code'); foreach ($boards as $code) { $xml .= $this->url("{$baseUrl}/board/{$code}", $today, 'daily', '0.8'); } // 게시글. 공지/전체공지도 실제로 열리는 페이지이므로 함께 넣는다. DB::table('tb_post') ->join('tb_board', 'tb_board.id', '=', 'tb_post.board_id') ->where('tb_board.is_display', '=', 1) ->where('tb_post.is_delete', '=', 0) ->where('tb_post.is_secret', '=', 0) ->where('tb_post.is_personal', '=', 0) ->select('tb_board.code', 'tb_post.id', 'tb_post.updated_at', 'tb_post.created_at') ->orderBy('tb_post.id') ->chunk(1000, function ($posts) use (&$xml, $baseUrl, $today) { foreach ($posts as $post) { $modified = $post->updated_at ?: $post->created_at; $lastModified = $modified ? date('Y-m-d', strtotime($modified)) : $today; if (!$this->append($xml, "{$baseUrl}/board/{$post->code}/{$post->id}", $lastModified, 'weekly', '0.6')) { return false; } } return true; }); // 영화 상세. // 뷰가 route('movie.search.show', base64_encode($row->movie_cd)) 로 링크를 만들므로 같은 규칙을 쓴다. DB::table('tb_movie') ->select('movie_cd') ->orderBy('movie_cd') ->chunk(1000, function ($movies) use (&$xml, $baseUrl, $today) { foreach ($movies as $movie) { if (!$movie->movie_cd) { continue; } $path = rawurlencode(base64_encode($movie->movie_cd)); if (!$this->append($xml, "{$baseUrl}/movie/search/{$path}", $today, 'weekly', '0.7')) { return false; } } return true; }); $xml .= '' . PHP_EOL; if ($this->truncated) { // 조용히 잘리면 커버리지가 줄어든 것을 알 수 없다. logger()->warning('[sitemap] URL 상한(' . self::MAX_URLS . ')에 도달해 일부가 제외되었습니다.'); } return $xml; } /** * 상한을 지키며 URL 을 덧붙인다. 상한에 닿으면 false 를 반환해 순회를 멈춘다. */ private function append(string &$xml, string $location, string $lastModified, string $changeFrequency, string $priority): bool { if ($this->count >= self::MAX_URLS) { $this->truncated = true; return false; } $xml .= $this->url($location, $lastModified, $changeFrequency, $priority); return true; } private function url(string $location, string $lastModified, string $changeFrequency, string $priority): string { $this->count++; return ' ' . PHP_EOL . ' ' . htmlspecialchars($location, ENT_XML1) . '' . PHP_EOL . ' ' . $lastModified . '' . PHP_EOL . ' ' . $changeFrequency . '' . PHP_EOL . ' ' . $priority . '' . PHP_EOL . ' ' . PHP_EOL; } }