From 2d75c5c9a7565f0ef60d4a0d139d8f9d000f99d0 Mon Sep 17 00:00:00 2001 From: jackfiled Date: Wed, 17 Jan 2024 13:20:32 +0800 Subject: [PATCH] =?UTF-8?q?dev:=20=E6=B8=B2=E6=9F=93=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=92=8C=E6=96=87=E4=BB=B6=E5=86=85=E5=AE=B9=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YaeBlog.Core/Exceptions/BlogFileException.cs | 19 +++++++ .../Extensions/BlogApplicationExtension.cs | 31 ++++++++++ YaeBlog.Core/Models/BlogContent.cs | 8 +++ YaeBlog.Core/Models/BlogEssay.cs | 10 ++++ YaeBlog.Core/Models/BlogOptions.cs | 10 ++++ YaeBlog.Core/Services/BlogHostedService.cs | 24 ++++++++ YaeBlog.Core/Services/EssayContentService.cs | 18 ++++++ YaeBlog.Core/Services/EssayScanService.cs | 57 +++++++++++++++++++ YaeBlog.Core/Services/RendererService.cs | 35 ++++++++++++ 9 files changed, 212 insertions(+) create mode 100644 YaeBlog.Core/Exceptions/BlogFileException.cs create mode 100644 YaeBlog.Core/Extensions/BlogApplicationExtension.cs create mode 100644 YaeBlog.Core/Models/BlogContent.cs create mode 100644 YaeBlog.Core/Models/BlogEssay.cs create mode 100644 YaeBlog.Core/Models/BlogOptions.cs create mode 100644 YaeBlog.Core/Services/BlogHostedService.cs create mode 100644 YaeBlog.Core/Services/EssayContentService.cs create mode 100644 YaeBlog.Core/Services/EssayScanService.cs create mode 100644 YaeBlog.Core/Services/RendererService.cs diff --git a/YaeBlog.Core/Exceptions/BlogFileException.cs b/YaeBlog.Core/Exceptions/BlogFileException.cs new file mode 100644 index 0000000..236f674 --- /dev/null +++ b/YaeBlog.Core/Exceptions/BlogFileException.cs @@ -0,0 +1,19 @@ +namespace YaeBlog.Core.Exceptions; + +public class BlogFileException : Exception +{ + public BlogFileException() : base() + { + + } + + public BlogFileException(string message) : base(message) + { + + } + + public BlogFileException(string message, Exception innerException) : base(message, innerException) + { + + } +} diff --git a/YaeBlog.Core/Extensions/BlogApplicationExtension.cs b/YaeBlog.Core/Extensions/BlogApplicationExtension.cs new file mode 100644 index 0000000..ae07567 --- /dev/null +++ b/YaeBlog.Core/Extensions/BlogApplicationExtension.cs @@ -0,0 +1,31 @@ +using Markdig; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using YaeBlog.Core.Builder; +using YaeBlog.Core.Models; +using YaeBlog.Core.Services; + +namespace YaeBlog.Core.Extensions; + +internal static class BlogApplicationExtension +{ + public static BlogApplicationBuilder ConfigureBlogApplication(this BlogApplicationBuilder builder) + { + builder.Configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true); + builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", + optional: true, reloadOnChange: true); + builder.Configuration.AddEnvironmentVariables(); + + builder.Services.Configure( + builder.Configuration.GetSection(BlogOptions.OptionName)); + + builder.Services.AddHostedService(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton( + _ => builder.MarkdigPipelineBuilder.Build()); + builder.Services.AddSingleton(); + + return builder; + } +} diff --git a/YaeBlog.Core/Models/BlogContent.cs b/YaeBlog.Core/Models/BlogContent.cs new file mode 100644 index 0000000..c8fa6c8 --- /dev/null +++ b/YaeBlog.Core/Models/BlogContent.cs @@ -0,0 +1,8 @@ +namespace YaeBlog.Core.Models; + +public class BlogContent +{ + public required string FileName { get; init; } + + public required string FileContent { get; init; } +} diff --git a/YaeBlog.Core/Models/BlogEssay.cs b/YaeBlog.Core/Models/BlogEssay.cs new file mode 100644 index 0000000..30e5923 --- /dev/null +++ b/YaeBlog.Core/Models/BlogEssay.cs @@ -0,0 +1,10 @@ +namespace YaeBlog.Core.Models; + +public class BlogEssay +{ + public required string Title { get; init; } + + public required DateTime PublishTime { get; init; } + + public required string HtmlContent { get; init; } +} diff --git a/YaeBlog.Core/Models/BlogOptions.cs b/YaeBlog.Core/Models/BlogOptions.cs new file mode 100644 index 0000000..712cbe4 --- /dev/null +++ b/YaeBlog.Core/Models/BlogOptions.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.Options; + +namespace YaeBlog.Core.Models; + +public class BlogOptions +{ + public const string OptionName = "Blog"; + + public required string Root { get; set; } +} diff --git a/YaeBlog.Core/Services/BlogHostedService.cs b/YaeBlog.Core/Services/BlogHostedService.cs new file mode 100644 index 0000000..b1e8734 --- /dev/null +++ b/YaeBlog.Core/Services/BlogHostedService.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace YaeBlog.Core.Services; + +public class BlogHostedService( + ILogger logger, + RendererService rendererService) : IHostedService +{ + public async Task StartAsync(CancellationToken cancellationToken) + { + logger.LogInformation("Welcome to YaeBlog!"); + + await rendererService.RenderAsync(); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + logger.LogInformation("YaeBlog stopped!\nHave a nice day!"); + return Task.CompletedTask; + } + + +} diff --git a/YaeBlog.Core/Services/EssayContentService.cs b/YaeBlog.Core/Services/EssayContentService.cs new file mode 100644 index 0000000..472126b --- /dev/null +++ b/YaeBlog.Core/Services/EssayContentService.cs @@ -0,0 +1,18 @@ +using System.Collections.Concurrent; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Services; + +public class EssayContentService +{ + private readonly ConcurrentDictionary _essays = new(); + + public bool TryGet(string key, out BlogEssay? essay) + => _essays.TryGetValue(key, out essay); + + public bool TryAdd(string key, BlogEssay essay) => _essays.TryAdd(key, essay); + + public IEnumerable> Essays => _essays; + + public int Count => _essays.Count; +} diff --git a/YaeBlog.Core/Services/EssayScanService.cs b/YaeBlog.Core/Services/EssayScanService.cs new file mode 100644 index 0000000..ca2910d --- /dev/null +++ b/YaeBlog.Core/Services/EssayScanService.cs @@ -0,0 +1,57 @@ +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using YaeBlog.Core.Exceptions; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Services; + +public class EssayScanService( + IOptions blogOptions, + ILogger logger) +{ + private readonly BlogOptions _blogOptions = blogOptions.Value; + + public async Task> ScanAsync() + { + string root = Path.Combine(Environment.CurrentDirectory, _blogOptions.Root); + DirectoryInfo rootDirectory = new(root); + + if (!rootDirectory.Exists) + { + throw new BlogFileException($"'{root}' is not a directory."); + } + + List markdownFiles = []; + + await Task.Run(() => + { + foreach (FileInfo fileInfo in rootDirectory.EnumerateFiles()) + { + if (fileInfo.Extension != ".md") + { + continue; + } + + logger.LogDebug("Scan markdown file: {}.", fileInfo.Name); + markdownFiles.Add(fileInfo); + } + }); + + ConcurrentBag contents = []; + + await Parallel.ForEachAsync(markdownFiles, async (info, token) => + { + StreamReader reader = new(info.OpenRead()); + + BlogContent content = new() + { + FileName = info.Name, FileContent = await reader.ReadToEndAsync(token) + }; + + contents.Add(content); + }); + + return contents.ToList(); + } +} diff --git a/YaeBlog.Core/Services/RendererService.cs b/YaeBlog.Core/Services/RendererService.cs new file mode 100644 index 0000000..507c676 --- /dev/null +++ b/YaeBlog.Core/Services/RendererService.cs @@ -0,0 +1,35 @@ +using System.Collections.Concurrent; +using Markdig; +using Microsoft.Extensions.Logging; +using YaeBlog.Core.Exceptions; +using YaeBlog.Core.Models; + +namespace YaeBlog.Core.Services; + +public class RendererService(ILogger logger, + EssayScanService essayScanService, + MarkdownPipeline markdownPipeline, + EssayContentService essayContentService) +{ + public async Task RenderAsync() + { + List contents = await essayScanService.ScanAsync(); + + Parallel.ForEach(contents, content => + { + logger.LogDebug("Render markdown file {}.", content.FileName); + BlogEssay essay = new() + { + Title = content.FileName, + PublishTime = DateTime.Now, + HtmlContent = Markdown.ToHtml(content.FileContent, markdownPipeline) + }; + + if (!essayContentService.TryAdd(essay.Title, essay)) + { + throw new BlogFileException( + $"There are two essays with the same name: '{content.FileName}'."); + } + }); + } +}