Codemaru 1.0.0.0
QR 코드, 모바일 랜딩 페이지, vCard 연락처 저장과 명함 디자인을 한 화면에서 제공하는 디지털 명함 서비스입니다.
로딩중...
검색중...
일치하는것 없음
SitePreviewService.cs
이 파일의 문서화 페이지로 가기
1using System.IO;
2using System.Net.Http;
3using System.Text.RegularExpressions;
4using Microsoft.Extensions.Configuration;
5using Microsoft.Extensions.Hosting;
6using Microsoft.Extensions.Logging;
7
8namespace Codemaru.Services;
9
18public sealed class SitePreviewService : BackgroundService
19{
28 private static readonly (string Key, string Url)[] Sites =
29 [
30 ("dreamine", "https://dreamine.kr/"),
31 ("wedding", "https://wedding.codemaru.co.kr"),
32 ("thankyou", "https://thankyou.codemaru.co.kr/"),
33 ("families", "https://families.codemaru.co.kr"),
34 ("portfolio", "https://portfolio.codemaru.co.kr/"),
35 ("cctv", "https://cctvviewer.codemaru.co.kr"),
36 ("shop", "https://shop.codemaru.co.kr/"),
37 ];
38
47 private static readonly Regex OgImageRegex = new(
48 @"<meta[^>]+property=[""']og:image[""'][^>]+content=[""']([^""']+)[""']",
49 RegexOptions.IgnoreCase | RegexOptions.Compiled);
50
59 private static readonly Regex OgImageRegex2 = new(
60 @"<meta[^>]+content=[""']([^""']+)[""'][^>]+property=[""']og:image[""']",
61 RegexOptions.IgnoreCase | RegexOptions.Compiled);
62
71 private readonly string _wwwRoot;
80 private readonly ILogger<SitePreviewService> _logger;
89 private readonly TimeSpan _interval;
98 private readonly HttpClient _http;
99
124 public SitePreviewService(ILogger<SitePreviewService> logger, IConfiguration config)
125 {
126 _logger = logger;
127 _interval = TimeSpan.FromHours(config.GetValue("SitePreview:RefreshHours", 6));
128 _wwwRoot = Path.Combine(AppContext.BaseDirectory, "wwwroot");
129 _http = new HttpClient(new HttpClientHandler { AllowAutoRedirect = true });
130 _http.DefaultRequestHeaders.UserAgent.ParseAdd(
131 "Mozilla/5.0 (compatible; CodemaruBot/1.0; +https://codemaru.co.kr)");
132 _http.Timeout = TimeSpan.FromSeconds(15);
133 }
134
159 protected override async Task ExecuteAsync(CancellationToken stoppingToken)
160 {
161 while (!stoppingToken.IsCancellationRequested)
162 {
163 await FetchAllAsync(stoppingToken);
164
165 try { await Task.Delay(_interval, stoppingToken); }
166 catch (OperationCanceledException) { break; }
167 }
168 }
169
194 private async Task FetchAllAsync(CancellationToken ct)
195 {
196 var outputDir = Path.Combine(_wwwRoot, "img", "previews");
197 Directory.CreateDirectory(outputDir);
198
199 foreach (var (key, url) in Sites)
200 {
201 if (ct.IsCancellationRequested) break;
202 await FetchOneAsync(key, url, outputDir, ct);
203 }
204 }
205
254 private async Task FetchOneAsync(string key, string siteUrl, string outputDir, CancellationToken ct)
255 {
256 var destPath = Path.Combine(outputDir, $"{key}.jpg");
257 var tempPath = destPath + ".tmp";
258
259 try
260 {
261 // 1. 사이트 HTML 가져오기
262 var html = await _http.GetStringAsync(siteUrl, ct);
263
264 // 2. og:image URL 파싱
265 var match = OgImageRegex.Match(html);
266 if (!match.Success) match = OgImageRegex2.Match(html);
267 if (!match.Success)
268 {
269 _logger.LogWarning("og:image 없음: {Key}", key);
270 return;
271 }
272
273 var imageUrl = match.Groups[1].Value.Trim();
274
275 // 상대 경로면 절대 경로로 변환
276 if (!imageUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
277 {
278 var base64 = new Uri(siteUrl);
279 imageUrl = new Uri(base64, imageUrl).ToString();
280 }
281
282 // 3. 이미지 다운로드
283 var imageBytes = await _http.GetByteArrayAsync(imageUrl, ct);
284 await File.WriteAllBytesAsync(tempPath, imageBytes, ct);
285 File.Move(tempPath, destPath, overwrite: true);
286
287 _logger.LogInformation("OG 이미지 캐시 완료: {Key} ← {ImageUrl}", key, imageUrl);
288 }
289 catch (Exception ex)
290 {
291 _logger.LogWarning(ex, "OG 이미지 fetch 실패: {Key} ({Url})", key, siteUrl);
292 if (File.Exists(tempPath)) File.Delete(tempPath);
293 }
294 }
295
304 public override void Dispose()
305 {
306 _http.Dispose();
307 base.Dispose();
308 }
309}
readonly ILogger< SitePreviewService > _logger
async Task FetchOneAsync(string key, string siteUrl, string outputDir, CancellationToken ct)
async Task FetchAllAsync(CancellationToken ct)
override async Task ExecuteAsync(CancellationToken stoppingToken)
static readonly(string Key, string Url)[] Sites
SitePreviewService(ILogger< SitePreviewService > logger, IConfiguration config)