add: 渲染前处理器和渲染后处理器

This commit is contained in:
jackfiled 2024-01-23 20:37:41 +08:00
parent 83a9a06fec
commit 2c6414dbea
5 changed files with 159 additions and 42 deletions

View File

@ -0,0 +1,10 @@
using YaeBlog.Core.Models;
namespace YaeBlog.Core.Abstractions;
public interface IPostRenderProcessor
{
BlogEssay Process(BlogEssay essay);
string Name { get; }
}

View File

@ -0,0 +1,10 @@
using YaeBlog.Core.Models;
namespace YaeBlog.Core.Abstractions;
public interface IPreRenderProcessor
{
BlogContent Process(BlogContent content);
string Name { get; }
}

View 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));
}
}

View File

@ -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);
} }
} }

View File

@ -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();