ShopPlatform.Web 1.0.0.0
농산물, 소프트웨어 라이선스와 개발 용역을 직접 판매하는 CodeMaru 직영 쇼핑몰입니다.
로딩중...
검색중...
일치하는것 없음
OgBotMiddleware.cs
이 파일의 문서화 페이지로 가기
3
5
14public sealed class OgBotMiddleware(RequestDelegate next)
15{
16 // 주요 SNS/메신저 크롤러 UA 키워드
25 private static readonly string[] BotKeywords =
26 [
27 "kakaotalk", "facebookexternalhit", "twitterbot", "linkedinbot",
28 "slackbot", "telegrambot", "whatsapp", "discordbot",
29 "baiduspider", "yeti", "naverbot", "daumoa",
30 "googlebot", "bingbot"
31 ];
32
73 public async Task InvokeAsync(HttpContext ctx, IShopTenantStore store, ShopOptions opts)
74 {
75 var ua = ctx.Request.Headers.UserAgent.ToString().ToLowerInvariant();
76 var path = ctx.Request.Path.Value ?? string.Empty;
77
78 // 봇이고 /{slug} 패턴이면 OG HTML 반환
79 if (IsBot(ua) && IsShopPath(path, out var slug))
80 {
81 var config = await store.GetAsync(slug);
82 if (config != null && config.IsActive)
83 {
84 await WriteOgHtml(ctx, config, opts, slug);
85 return;
86 }
87 }
88
89 await next(ctx);
90 }
91
116 private static bool IsBot(string ua)
117 => BotKeywords.Any(k => ua.Contains(k));
118
127 private static readonly HashSet<string> Reserved = new(StringComparer.OrdinalIgnoreCase)
128 {
129 "admin", "pay", "healthz", "_framework", "_blazor",
130 "css", "js", "img", "shop-img", "favicon.ico"
131 };
132
165 private static bool IsShopPath(string path, out string slug)
166 {
167 slug = string.Empty;
168 var trimmed = path.Trim('/');
169 // 루트(/) 또는 하위경로 제외, 단일 세그먼트만 처리
170 if (string.IsNullOrEmpty(trimmed) || trimmed.Contains('/')) return false;
171 if (Reserved.Contains(trimmed)) return false;
172 slug = trimmed;
173 return true;
174 }
175
224 private static async Task WriteOgHtml(HttpContext ctx, ShopConfig cfg, ShopOptions opts, string slug)
225 {
226 var baseUrl = opts.BaseUrl.TrimEnd('/');
227 var title = !string.IsNullOrEmpty(cfg.OgTitle) ? cfg.OgTitle : cfg.ShopName;
228 var desc = !string.IsNullOrEmpty(cfg.OgDescription) ? cfg.OgDescription
229 : !string.IsNullOrEmpty(cfg.Description) ? cfg.Description
230 : $"{cfg.ShopName}에서 쇼핑하세요";
231 var url = $"{baseUrl}/{slug}";
232
233 // OG 이미지: OgImagePath → LogoPath → 자동 생성
234 string imgUrl;
235 if (!string.IsNullOrEmpty(cfg.OgImagePath))
236 imgUrl = cfg.OgImagePath.StartsWith("http") ? cfg.OgImagePath : $"{baseUrl}{cfg.OgImagePath}";
237 else if (!string.IsNullOrEmpty(cfg.LogoPath))
238 imgUrl = cfg.LogoPath.StartsWith("http") ? cfg.LogoPath : $"{baseUrl}{cfg.LogoPath}";
239 else
240 imgUrl = $"{baseUrl}/img/og/og-{slug}.png";
241
242 var html = $"""
243 <!DOCTYPE html>
244 <html lang="ko">
245 <head>
246 <meta charset="utf-8" />
247 <title>{HtmlEncode(title)}</title>
248 <meta name="description" content="{HtmlEncode(desc)}" />
249 <meta property="og:type" content="website" />
250 <meta property="og:url" content="{url}" />
251 <meta property="og:title" content="{HtmlEncode(title)}" />
252 <meta property="og:description" content="{HtmlEncode(desc)}" />
253 <meta property="og:image" content="{imgUrl}" />
254 <meta property="og:image:width" content="1200" />
255 <meta property="og:image:height" content="630" />
256 <meta property="og:site_name" content="ShopPlatform" />
257 <meta name="twitter:card" content="summary_large_image" />
258 <meta name="twitter:title" content="{HtmlEncode(title)}" />
259 <meta name="twitter:description" content="{HtmlEncode(desc)}" />
260 <meta name="twitter:image" content="{imgUrl}" />
261 </head>
262 <body>
263 <script>location.href="{url}";</script>
264 <a href="{url}">{HtmlEncode(title)}</a>
265 </body>
266 </html>
267 """;
268
269 ctx.Response.ContentType = "text/html; charset=utf-8";
270 ctx.Response.StatusCode = 200;
271 await ctx.Response.WriteAsync(html);
272 }
273
298 private static string HtmlEncode(string s)
299 => System.Net.WebUtility.HtmlEncode(s);
300}
static async Task WriteOgHtml(HttpContext ctx, ShopConfig cfg, ShopOptions opts, string slug)
static readonly HashSet< string > Reserved
async Task InvokeAsync(HttpContext ctx, IShopTenantStore store, ShopOptions opts)
static bool IsShopPath(string path, out string slug)
Task< ShopConfig?> GetAsync(string slug)