-
Notifications
You must be signed in to change notification settings - Fork 2
release: dev를 main에 반영 #227
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
804d485
82216b9
820acc3
684b389
48d4648
514a025
81349a9
6178ec4
c5a9701
f710724
c730cf0
4a59c2b
b1f4505
b17cdd1
ca4fbe7
dad5519
9c69881
a166aee
977a64f
2822909
ec04545
fc37de5
402a020
58bacf7
8122597
dd3b45f
9be0647
989d7da
92daab1
8a72515
caec61b
d3ae95f
bda114d
efbc24a
38f471e
d15fe44
7d89e00
8923fb6
74afca6
d8e6d61
d01ad85
f14e3c1
1698596
acf49a0
4584415
66ec075
be8a5bc
4e82a60
f184f1f
fdc7c1b
4e92e69
dc87ee9
2bd85da
ccfcfa3
0c7b91d
5cb8673
a9f992e
bfcd2c7
04530af
286372a
41b39ff
46629b6
2b149e7
0b9b809
d16a0af
84623b6
7651100
cea6178
260a4f4
3065f73
e26c2d3
b2c63dc
2ebdb71
ef04bea
c1eb845
ad4fc47
4999f8c
e650e99
ffaa557
7fea5af
aed907d
4cd2f71
4590da1
fd32da5
efc7c46
068bf6c
acc6f97
48161b1
f999c18
485bce4
892d317
9b0e007
d6fb1d1
d9a0eb0
83ebe70
ec6b7ac
d9c883e
4c15b62
6a948b4
6fa6b08
85837a6
53471de
d1d5d57
1b4c43d
7ed93e2
6ab54cc
d742237
0f47087
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.bop.youthpick.global.config; | ||
|
|
||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
|
||
| /** 프론트엔드 배포 도메인. sitemap.xml처럼 절대 URL을 만들어야 하는 곳에서 쓴다. */ | ||
| @ConfigurationProperties(prefix = "youthpick.site") | ||
| public record SiteProperties(String frontendUrl) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.bop.youthpick.sitemap.controller; | ||
|
|
||
| import com.bop.youthpick.sitemap.service.SitemapService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| /** | ||
| * sitemap.xml은 URL 프로토콜 특성상 사이트 루트("/sitemap.xml")에서 서빙해야 하므로 다른 컨트롤러처럼 {@code /api/v1} 아래 두지 않는다. | ||
| * nginx가 이 경로를 백엔드로 프록시한다(front-end/nginx.conf 참고). | ||
| */ | ||
| @Tag(name = "SEO") | ||
| @RestController | ||
| @RequiredArgsConstructor | ||
| public class SitemapController { | ||
|
|
||
| private final SitemapService sitemapService; | ||
|
|
||
| @Operation( | ||
| summary = "sitemap.xml 조회", | ||
| description = "정적 페이지 + 공개 커뮤니티 게시글 URL을 담은 sitemap을 반환한다.") | ||
| @GetMapping(value = "/sitemap.xml", produces = MediaType.APPLICATION_XML_VALUE) | ||
| public ResponseEntity<String> sitemap() { | ||
| return ResponseEntity.ok(sitemapService.generate()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package com.bop.youthpick.sitemap.service; | ||
|
|
||
| import com.bop.youthpick.global.config.SiteProperties; | ||
| import com.bop.youthpick.post.repository.PostRepository; | ||
| import java.time.format.DateTimeFormatter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| /** | ||
| * 검색엔진(Search Console)에 등록할 sitemap.xml을 요청마다 생성한다. 프론트는 SSR 없는 Vite SPA라 빌드 시점에는 게시글처럼 동적인 URL을 알 | ||
| * 수 없으므로, 백엔드가 DB를 조회해 매 요청 최신 목록으로 만들어 낸다. | ||
| */ | ||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class SitemapService { | ||
|
|
||
| private static final DateTimeFormatter LASTMOD_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE; | ||
|
|
||
| private final PostRepository postRepository; | ||
| private final SiteProperties siteProperties; | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public String generate() { | ||
| String baseUrl = siteProperties.frontendUrl(); | ||
| StringBuilder xml = new StringBuilder(); | ||
| xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | ||
| xml.append("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"); | ||
|
|
||
| appendUrl(xml, baseUrl + "/", null, "daily", "1.0"); | ||
| appendUrl(xml, baseUrl + "/search", null, "daily", "0.8"); | ||
| appendUrl(xml, baseUrl + "/community", null, "hourly", "0.8"); | ||
|
|
||
| for (PostRepository.PostSitemapView post : | ||
| postRepository.findAllByDeletedAtIsNullOrderByUpdatedAtDesc()) { | ||
| String lastmod = LASTMOD_FORMATTER.format(post.getUpdatedAt()); | ||
| appendUrl(xml, baseUrl + "/community/" + post.getId(), lastmod, "weekly", "0.6"); | ||
| } | ||
|
|
||
| xml.append("</urlset>\n"); | ||
| return xml.toString(); | ||
| } | ||
|
|
||
| private void appendUrl( | ||
| StringBuilder xml, String loc, String lastmod, String changefreq, String priority) { | ||
| xml.append(" <url>\n"); | ||
| xml.append(" <loc>").append(loc).append("</loc>\n"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: XML 특수문자 이스케이프 누락
Reply with |
||
| if (lastmod != null) { | ||
| xml.append(" <lastmod>").append(lastmod).append("</lastmod>\n"); | ||
| } | ||
| xml.append(" <changefreq>").append(changefreq).append("</changefreq>\n"); | ||
| xml.append(" <priority>").append(priority).append("</priority>\n"); | ||
| xml.append(" </url>\n"); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: sitemap.xml URL 수 제한 누락
sitemap.xml은 파일당 최대 50,000개 URL 허용(Google/Bing 공통 규칙)인데, 현재 모든 게시글을 제한 없이 하나의 sitemap에 넣는다. 게시글이 5만 개를 넘어가면 검색엔진이 sitemap을 거부한다.findAllByDeletedAtIsNullOrderByUpdatedAtDesc()대신 페이지네이션 + sitemap index(<sitemapindex>)를 도입하거나, 상한을 둬서 5만 개 이하로 자른다.Reply with
@kilocode-bot fix itto have Kilo Code address this issue.