add: 渲染前处理器和渲染后处理器
This commit is contained in:
		
							
								
								
									
										10
									
								
								YaeBlog.Core/Abstractions/IPostRenderProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								YaeBlog.Core/Abstractions/IPostRenderProcessor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| using YaeBlog.Core.Models; | ||||
|  | ||||
| namespace YaeBlog.Core.Abstractions; | ||||
|  | ||||
| public interface IPostRenderProcessor | ||||
| { | ||||
|     BlogEssay Process(BlogEssay essay); | ||||
|  | ||||
|     string Name { get; } | ||||
| } | ||||
							
								
								
									
										10
									
								
								YaeBlog.Core/Abstractions/IPreRenderProcessor.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								YaeBlog.Core/Abstractions/IPreRenderProcessor.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| using YaeBlog.Core.Models; | ||||
|  | ||||
| namespace YaeBlog.Core.Abstractions; | ||||
|  | ||||
| public interface IPreRenderProcessor | ||||
| { | ||||
|     BlogContent Process(BlogContent content); | ||||
|  | ||||
|     string Name { get; } | ||||
| } | ||||
							
								
								
									
										47
									
								
								YaeBlog.Core/Extensions/BlogApplicationBuilderExtension.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								YaeBlog.Core/Extensions/BlogApplicationBuilderExtension.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| using Markdig; | ||||
| using Microsoft.AspNetCore.Builder; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using YaeBlog.Core.Builder; | ||||
| using YaeBlog.Core.Models; | ||||
| using YaeBlog.Core.Services; | ||||
| using YamlDotNet.Serialization; | ||||
| using YamlDotNet.Serialization.NamingConventions; | ||||
|  | ||||
| namespace YaeBlog.Core.Extensions; | ||||
|  | ||||
| public static class BlogApplicationBuilderExtension | ||||
| { | ||||
|     internal static void 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<BlogOptions>( | ||||
|             builder.Configuration.GetSection(BlogOptions.OptionName)); | ||||
|  | ||||
|         builder.YamlDeserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); | ||||
|         builder.YamlDeserializerBuilder.IgnoreUnmatchedProperties(); | ||||
|  | ||||
|         builder.Services.AddSingleton<MarkdownPipeline>( | ||||
|             _ => builder.MarkdigPipelineBuilder.Build()); | ||||
|         builder.Services.AddSingleton<IDeserializer>( | ||||
|             _ => builder.YamlDeserializerBuilder.Build()); | ||||
|  | ||||
|         builder.Services.AddHostedService<BlogHostedService>(); | ||||
|         builder.Services.AddSingleton<EssayScanService>(); | ||||
|         builder.Services.AddSingleton<RendererService>(); | ||||
|         builder.Services.AddSingleton<EssayContentService>(); | ||||
|     } | ||||
|  | ||||
|     public static void ConfigureWebApplication(this BlogApplicationBuilder builder, | ||||
|         Action<WebApplicationBuilder> configureWebApplicationBuilder, | ||||
|         Action<WebApplication> configureWebApplication) | ||||
|     { | ||||
|         builder.Services.AddHostedService<WebApplicationHostedService>(provider => | ||||
|             new WebApplicationHostedService(configureWebApplicationBuilder, | ||||
|             configureWebApplication, provider)); | ||||
|     } | ||||
| } | ||||
| @@ -1,47 +1,29 @@ | ||||
| using Markdig; | ||||
| using Microsoft.AspNetCore.Builder; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using YaeBlog.Core.Abstractions; | ||||
| using YaeBlog.Core.Builder; | ||||
| using YaeBlog.Core.Models; | ||||
| using YaeBlog.Core.Services; | ||||
| using YamlDotNet.Serialization; | ||||
| using YamlDotNet.Serialization.NamingConventions; | ||||
|  | ||||
| namespace YaeBlog.Core.Extensions; | ||||
|  | ||||
| public static class BlogApplicationExtension | ||||
| { | ||||
|     internal static void ConfigureBlogApplication(this BlogApplicationBuilder builder) | ||||
|     public static void UsePreRenderProcessor<T>(this BlogApplication application) | ||||
|         where T : IPreRenderProcessor | ||||
|     { | ||||
|         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<BlogOptions>( | ||||
|             builder.Configuration.GetSection(BlogOptions.OptionName)); | ||||
|  | ||||
|         builder.YamlDeserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance); | ||||
|         builder.YamlDeserializerBuilder.IgnoreUnmatchedProperties(); | ||||
|  | ||||
|         builder.Services.AddSingleton<MarkdownPipeline>( | ||||
|             _ => builder.MarkdigPipelineBuilder.Build()); | ||||
|         builder.Services.AddSingleton<IDeserializer>( | ||||
|             _ => builder.YamlDeserializerBuilder.Build()); | ||||
|  | ||||
|         builder.Services.AddHostedService<BlogHostedService>(); | ||||
|         builder.Services.AddSingleton<EssayScanService>(); | ||||
|         builder.Services.AddSingleton<RendererService>(); | ||||
|         builder.Services.AddSingleton<EssayContentService>(); | ||||
|         RendererService rendererService = | ||||
|             application.Services.GetRequiredService<RendererService>(); | ||||
|         T preRenderProcessor = | ||||
|             application.Services.GetRequiredService<T>(); | ||||
|         rendererService.AddPreRenderProcessor(preRenderProcessor); | ||||
|     } | ||||
|  | ||||
|     public static void ConfigureWebApplication(this BlogApplicationBuilder builder, | ||||
|         Action<WebApplicationBuilder> configureWebApplicationBuilder, | ||||
|         Action<WebApplication> configureWebApplication) | ||||
|     public static void UsePostRenderProcessor<T>(this BlogApplication application) | ||||
|         where T : IPostRenderProcessor | ||||
|     { | ||||
|         builder.Services.AddHostedService<WebApplicationHostedService>(provider => | ||||
|             new WebApplicationHostedService(configureWebApplicationBuilder, | ||||
|             configureWebApplication, provider)); | ||||
|         RendererService rendererService = | ||||
|             application.Services.GetRequiredService<RendererService>(); | ||||
|         T postRenderProcessor = | ||||
|             application.Services.GetRequiredService<T>(); | ||||
|         rendererService.AddPostRenderProcessor(postRenderProcessor); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| using System.Diagnostics; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Diagnostics; | ||||
| using Markdig; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using YaeBlog.Core.Abstractions; | ||||
| using YaeBlog.Core.Exceptions; | ||||
| using YaeBlog.Core.Models; | ||||
| using YamlDotNet.Core; | ||||
| @@ -16,18 +18,23 @@ public class RendererService(ILogger<RendererService> logger, | ||||
| { | ||||
|     private readonly Stopwatch _stopwatch = new(); | ||||
|  | ||||
|     private readonly List<IPreRenderProcessor> _preRenderProcessors = []; | ||||
|  | ||||
|     private readonly List<IPostRenderProcessor> _postRenderProcessors = []; | ||||
|  | ||||
|     public async Task RenderAsync() | ||||
|     { | ||||
|         _stopwatch.Start(); | ||||
|         logger.LogInformation("Render essays start."); | ||||
|  | ||||
|         List<BlogContent> contents = await essayScanService.ScanAsync(); | ||||
|         IEnumerable<BlogContent> preProcessedContents = | ||||
|             PreProcess(contents); | ||||
|  | ||||
|         List<BlogEssay> essays = []; | ||||
|  | ||||
|         await Task.Run(() => | ||||
|         { | ||||
|             foreach (BlogContent content in contents) | ||||
|             foreach (BlogContent content in preProcessedContents) | ||||
|             { | ||||
|                 MarkdownMetadata? metadata = TryParseMetadata(content); | ||||
|                 BlogEssay essay = new() | ||||
| @@ -46,6 +53,7 @@ public class RendererService(ILogger<RendererService> logger, | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         ConcurrentBag<BlogEssay> postProcessEssays = []; | ||||
|         Parallel.ForEach(essays, essay => | ||||
|         { | ||||
|  | ||||
| @@ -58,19 +66,79 @@ public class RendererService(ILogger<RendererService> logger, | ||||
|             }; | ||||
|             newEssay.Tags.AddRange(essay.Tags); | ||||
|  | ||||
|             if (!essayContentService.TryAdd(newEssay.FileName, newEssay)) | ||||
|             { | ||||
|                 throw new BlogFileException( | ||||
|                     $"There are two essays with the same name: '{newEssay.FileName}'."); | ||||
|             } | ||||
|             postProcessEssays.Add(newEssay); | ||||
|             logger.LogDebug("Render markdown file {}.", newEssay); | ||||
|         }); | ||||
|  | ||||
|         PostProcess(postProcessEssays); | ||||
|  | ||||
|         _stopwatch.Stop(); | ||||
|         logger.LogInformation("Render finished, consuming {} s.", | ||||
|             _stopwatch.Elapsed.ToString("s\\.fff")); | ||||
|     } | ||||
|  | ||||
|     public void AddPreRenderProcessor(IPreRenderProcessor processor) | ||||
|     { | ||||
|         bool exist = _preRenderProcessors.Any(p => p.Name == processor.Name); | ||||
|  | ||||
|         if (exist) | ||||
|         { | ||||
|             throw new InvalidOperationException("There exists one pre-render processor " + | ||||
|                                                 $"with the same name: {processor.Name}."); | ||||
|         } | ||||
|  | ||||
|         _preRenderProcessors.Add(processor); | ||||
|     } | ||||
|  | ||||
|     public void AddPostRenderProcessor(IPostRenderProcessor processor) | ||||
|     { | ||||
|         bool exist = _postRenderProcessors.Any(p => p.Name == processor.Name); | ||||
|  | ||||
|         if (exist) | ||||
|         { | ||||
|             throw new InvalidCastException("There exists one post-render processor " + | ||||
|                                            $"with the same name: {processor.Name}."); | ||||
|         } | ||||
|  | ||||
|         _postRenderProcessors.Add(processor); | ||||
|     } | ||||
|  | ||||
|     private IEnumerable<BlogContent> PreProcess(IEnumerable<BlogContent> contents) | ||||
|     { | ||||
|         ConcurrentBag<BlogContent> processedContents = []; | ||||
|  | ||||
|         Parallel.ForEach(contents, content => | ||||
|         { | ||||
|             foreach (IPreRenderProcessor processor in _preRenderProcessors) | ||||
|             { | ||||
|                 content = processor.Process(content); | ||||
|             } | ||||
|  | ||||
|             processedContents.Add(content); | ||||
|         }); | ||||
|  | ||||
|         return processedContents; | ||||
|     } | ||||
|  | ||||
|     private void PostProcess(IEnumerable<BlogEssay> essays) | ||||
|     { | ||||
|         Parallel.ForEach(essays, essay => | ||||
|         { | ||||
|             foreach (IPostRenderProcessor processor in _postRenderProcessors) | ||||
|             { | ||||
|                 essay = processor.Process(essay); | ||||
|             } | ||||
|  | ||||
|             if (!essayContentService.TryAdd(essay.FileName, essay)) | ||||
|             { | ||||
|                 throw new BlogFileException( | ||||
|                     $"There are two essays with the same name: '{essay.FileName}'."); | ||||
|             } | ||||
|  | ||||
|             logger.LogDebug("Post-Process essay: {}.", essay); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private MarkdownMetadata? TryParseMetadata(BlogContent content) | ||||
|     { | ||||
|         string fileContent = content.FileContent.Trim(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user