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.Extensions.DependencyInjection; | ||||||
| using Microsoft.AspNetCore.Builder; | using YaeBlog.Core.Abstractions; | ||||||
| using Microsoft.Extensions.Configuration; |  | ||||||
| using Microsoft.Extensions.DependencyInjection; |  | ||||||
| using YaeBlog.Core.Builder; | using YaeBlog.Core.Builder; | ||||||
| using YaeBlog.Core.Models; |  | ||||||
| using YaeBlog.Core.Services; | using YaeBlog.Core.Services; | ||||||
| using YamlDotNet.Serialization; |  | ||||||
| using YamlDotNet.Serialization.NamingConventions; |  | ||||||
|  |  | ||||||
| namespace YaeBlog.Core.Extensions; | namespace YaeBlog.Core.Extensions; | ||||||
|  |  | ||||||
| public static class BlogApplicationExtension | 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); |         RendererService rendererService = | ||||||
|         builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", |             application.Services.GetRequiredService<RendererService>(); | ||||||
|             optional: true, reloadOnChange: true); |         T preRenderProcessor = | ||||||
|         builder.Configuration.AddEnvironmentVariables(); |             application.Services.GetRequiredService<T>(); | ||||||
|  |         rendererService.AddPreRenderProcessor(preRenderProcessor); | ||||||
|         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, |     public static void UsePostRenderProcessor<T>(this BlogApplication application) | ||||||
|         Action<WebApplicationBuilder> configureWebApplicationBuilder, |         where T : IPostRenderProcessor | ||||||
|         Action<WebApplication> configureWebApplication) |  | ||||||
|     { |     { | ||||||
|         builder.Services.AddHostedService<WebApplicationHostedService>(provider => |         RendererService rendererService = | ||||||
|             new WebApplicationHostedService(configureWebApplicationBuilder, |             application.Services.GetRequiredService<RendererService>(); | ||||||
|             configureWebApplication, provider)); |         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 Markdig; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
|  | using YaeBlog.Core.Abstractions; | ||||||
| using YaeBlog.Core.Exceptions; | using YaeBlog.Core.Exceptions; | ||||||
| using YaeBlog.Core.Models; | using YaeBlog.Core.Models; | ||||||
| using YamlDotNet.Core; | using YamlDotNet.Core; | ||||||
| @@ -16,18 +18,23 @@ public class RendererService(ILogger<RendererService> logger, | |||||||
| { | { | ||||||
|     private readonly Stopwatch _stopwatch = new(); |     private readonly Stopwatch _stopwatch = new(); | ||||||
|  |  | ||||||
|  |     private readonly List<IPreRenderProcessor> _preRenderProcessors = []; | ||||||
|  |  | ||||||
|  |     private readonly List<IPostRenderProcessor> _postRenderProcessors = []; | ||||||
|  |  | ||||||
|     public async Task RenderAsync() |     public async Task RenderAsync() | ||||||
|     { |     { | ||||||
|         _stopwatch.Start(); |         _stopwatch.Start(); | ||||||
|         logger.LogInformation("Render essays start."); |         logger.LogInformation("Render essays start."); | ||||||
|  |  | ||||||
|         List<BlogContent> contents = await essayScanService.ScanAsync(); |         List<BlogContent> contents = await essayScanService.ScanAsync(); | ||||||
|  |         IEnumerable<BlogContent> preProcessedContents = | ||||||
|  |             PreProcess(contents); | ||||||
|  |  | ||||||
|         List<BlogEssay> essays = []; |         List<BlogEssay> essays = []; | ||||||
|  |  | ||||||
|         await Task.Run(() => |         await Task.Run(() => | ||||||
|         { |         { | ||||||
|             foreach (BlogContent content in contents) |             foreach (BlogContent content in preProcessedContents) | ||||||
|             { |             { | ||||||
|                 MarkdownMetadata? metadata = TryParseMetadata(content); |                 MarkdownMetadata? metadata = TryParseMetadata(content); | ||||||
|                 BlogEssay essay = new() |                 BlogEssay essay = new() | ||||||
| @@ -46,6 +53,7 @@ public class RendererService(ILogger<RendererService> logger, | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         ConcurrentBag<BlogEssay> postProcessEssays = []; | ||||||
|         Parallel.ForEach(essays, essay => |         Parallel.ForEach(essays, essay => | ||||||
|         { |         { | ||||||
|  |  | ||||||
| @@ -58,19 +66,79 @@ public class RendererService(ILogger<RendererService> logger, | |||||||
|             }; |             }; | ||||||
|             newEssay.Tags.AddRange(essay.Tags); |             newEssay.Tags.AddRange(essay.Tags); | ||||||
|  |  | ||||||
|             if (!essayContentService.TryAdd(newEssay.FileName, newEssay)) |             postProcessEssays.Add(newEssay); | ||||||
|             { |  | ||||||
|                 throw new BlogFileException( |  | ||||||
|                     $"There are two essays with the same name: '{newEssay.FileName}'."); |  | ||||||
|             } |  | ||||||
|             logger.LogDebug("Render markdown file {}.", newEssay); |             logger.LogDebug("Render markdown file {}.", newEssay); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         PostProcess(postProcessEssays); | ||||||
|  |  | ||||||
|         _stopwatch.Stop(); |         _stopwatch.Stop(); | ||||||
|         logger.LogInformation("Render finished, consuming {} s.", |         logger.LogInformation("Render finished, consuming {} s.", | ||||||
|             _stopwatch.Elapsed.ToString("s\\.fff")); |             _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) |     private MarkdownMetadata? TryParseMetadata(BlogContent content) | ||||||
|     { |     { | ||||||
|         string fileContent = content.FileContent.Trim(); |         string fileContent = content.FileContent.Trim(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user