dev: 渲染服务和文件内容服务

This commit is contained in:
jackfiled 2024-01-17 13:20:32 +08:00
parent 3d811770c4
commit 2d75c5c9a7
9 changed files with 212 additions and 0 deletions

View File

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

View File

@ -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<BlogOptions>(
builder.Configuration.GetSection(BlogOptions.OptionName));
builder.Services.AddHostedService<BlogHostedService>();
builder.Services.AddSingleton<EssayScanService>();
builder.Services.AddSingleton<RendererService>();
builder.Services.AddSingleton<MarkdownPipeline>(
_ => builder.MarkdigPipelineBuilder.Build());
builder.Services.AddSingleton<EssayContentService>();
return builder;
}
}

View File

@ -0,0 +1,8 @@
namespace YaeBlog.Core.Models;
public class BlogContent
{
public required string FileName { get; init; }
public required string FileContent { get; init; }
}

View File

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

View File

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

View File

@ -0,0 +1,24 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace YaeBlog.Core.Services;
public class BlogHostedService(
ILogger<BlogHostedService> 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;
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Concurrent;
using YaeBlog.Core.Models;
namespace YaeBlog.Core.Services;
public class EssayContentService
{
private readonly ConcurrentDictionary<string, BlogEssay> _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<KeyValuePair<string, BlogEssay>> Essays => _essays;
public int Count => _essays.Count;
}

View File

@ -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> blogOptions,
ILogger<EssayContentService> logger)
{
private readonly BlogOptions _blogOptions = blogOptions.Value;
public async Task<List<BlogContent>> 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<FileInfo> 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<BlogContent> 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();
}
}

View File

@ -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<RendererService> logger,
EssayScanService essayScanService,
MarkdownPipeline markdownPipeline,
EssayContentService essayContentService)
{
public async Task RenderAsync()
{
List<BlogContent> 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}'.");
}
});
}
}