Compare commits
1 Commits
master
...
fda4c01c22
| Author | SHA1 | Date | |
|---|---|---|---|
| fda4c01c22 |
@@ -12,10 +12,7 @@ indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{project.json,appsettings.json,appsettings.*.json}]
|
||||
indent_size = 2
|
||||
|
||||
[*.{yaml,yml}]
|
||||
[project.json]
|
||||
indent_size = 2
|
||||
|
||||
# C# and Visual Basic files
|
||||
|
||||
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -1,5 +1,2 @@
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||
*.avif filter=lfs diff=lfs merge=lfs -text
|
||||
*.webp filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
name: Build blog docker image
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
Build-Blog-Image:
|
||||
runs-on: archlinux
|
||||
steps:
|
||||
- name: Check out code.
|
||||
uses: http://github-mirrors.infra.svc.cluster.local/actions/checkout.git@v4
|
||||
with:
|
||||
lfs: true
|
||||
- name: Build project.
|
||||
run: |
|
||||
git submodule update --init
|
||||
podman pull mcr.azure.cn/dotnet/aspnet:10.0
|
||||
pwsh build.ps1 build
|
||||
- name: Workaround to make sure podman-login working.
|
||||
run: |
|
||||
mkdir -p /root/.docker
|
||||
- name: Login tencent cloud docker registry.
|
||||
uses: http://github-mirrors.infra.svc.cluster.local/actions/podman-login.git@v1
|
||||
with:
|
||||
registry: ccr.ccs.tencentyun.com
|
||||
username: 100044380877
|
||||
password: ${{ secrets.TENCENT_REGISTRY_PASSWORD }}
|
||||
auth_file_path: /etc/containers/auth.json
|
||||
- name: Push docker image.
|
||||
run: podman push ccr.ccs.tencentyun.com/jackfiled/blog:latest
|
||||
Build-Blog-Image:
|
||||
runs-on: archlinux
|
||||
steps:
|
||||
- uses: https://mirrors.rrricardo.top/actions/checkout.git@v4
|
||||
name: Check out code
|
||||
with:
|
||||
lfs: true
|
||||
- name: Build project
|
||||
run: |
|
||||
cd YaeBlog
|
||||
dotnet publish
|
||||
- name: Build docker image
|
||||
run: |
|
||||
cd YaeBlog
|
||||
podman build . -t registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
|
||||
- name: Workaround to make sure podman login succeed
|
||||
run: |
|
||||
mkdir /root/.docker
|
||||
- name: Login aliyun docker registry
|
||||
uses: https://mirrors.rrricardo.top/actions/podman-login.git@v1
|
||||
with:
|
||||
registry: registry.cn-beijing.aliyuncs.com
|
||||
username: 初冬的朝阳
|
||||
password: ${{ secrets.ALIYUN_PASSWORD }}
|
||||
auth_file_path: /etc/containers/auth.json
|
||||
- name: Push docker image
|
||||
run: podman push registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -184,7 +184,6 @@ DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
out/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "third-party/BlazorSvgComponents"]
|
||||
path = third-party/BlazorSvgComponents
|
||||
url = https://git.rrricardo.top/jackfiled/BlazorSvgComponents.git
|
||||
11
YaeBlog.slnx
11
YaeBlog.slnx
@@ -7,17 +7,8 @@
|
||||
<File Path=".editorconfig" />
|
||||
<File Path=".gitattributes" />
|
||||
<File Path=".gitignore" />
|
||||
<File Path="build.ps1" />
|
||||
<File Path="LICENSE" />
|
||||
<File Path="README.md" />
|
||||
</Folder>
|
||||
<Folder Name="/src/">
|
||||
<Project Path="src/YaeBlog.Tests/YaeBlog.Tests.csproj" />
|
||||
<Project Path="src/YaeBlog/YaeBlog.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/third-party/" />
|
||||
<Folder Name="/third-party/BlazorSvgComponents/" />
|
||||
<Folder Name="/third-party/BlazorSvgComponents/src/">
|
||||
<Project Path="third-party/BlazorSvgComponents/src/BlazorSvgComponents/BlazorSvgComponents.csproj" />
|
||||
</Folder>
|
||||
<Project Path="YaeBlog/YaeBlog.csproj" />
|
||||
</Solution>
|
||||
|
||||
10
YaeBlog/Abstraction/IEssayScanService.cs
Normal file
10
YaeBlog/Abstraction/IEssayScanService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using YaeBlog.Models;
|
||||
|
||||
namespace YaeBlog.Abstraction;
|
||||
|
||||
public interface IEssayScanService
|
||||
{
|
||||
public Task<BlogContents> ScanContents();
|
||||
|
||||
public Task SaveBlogContent(BlogContent content, bool isDraft = true);
|
||||
}
|
||||
36
YaeBlog/Commands/Binders/BlogOptionsBinder.cs
Normal file
36
YaeBlog/Commands/Binders/BlogOptionsBinder.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.CommandLine.Binding;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
using YaeBlog.Models;
|
||||
|
||||
namespace YaeBlog.Commands.Binders;
|
||||
|
||||
public sealed class BlogOptionsBinder : BinderBase<IOptions<BlogOptions>>
|
||||
{
|
||||
protected override IOptions<BlogOptions> GetBoundValue(BindingContext bindingContext)
|
||||
{
|
||||
bindingContext.AddService<IOptions<BlogOptions>>(_ =>
|
||||
{
|
||||
FileInfo settings = new(Path.Combine(Environment.CurrentDirectory, "appsettings.json"));
|
||||
if (!settings.Exists)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to load YaeBlog configurations.");
|
||||
}
|
||||
|
||||
using StreamReader reader = settings.OpenText();
|
||||
using JsonDocument document = JsonDocument.Parse(reader.ReadToEnd());
|
||||
JsonElement root = document.RootElement;
|
||||
JsonElement optionSection = root.GetProperty(BlogOptions.OptionName);
|
||||
|
||||
BlogOptions? result = optionSection.Deserialize<BlogOptions>();
|
||||
if (result is null)
|
||||
{
|
||||
throw new InvalidOperationException("Failed to load YaeBlog configuration in appsettings.json.");
|
||||
}
|
||||
|
||||
return new OptionsWrapper<BlogOptions>(result);
|
||||
});
|
||||
|
||||
return bindingContext.GetRequiredService<IOptions<BlogOptions>>();
|
||||
}
|
||||
}
|
||||
32
YaeBlog/Commands/Binders/EssayScanServiceBinder.cs
Normal file
32
YaeBlog/Commands/Binders/EssayScanServiceBinder.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.CommandLine.Binding;
|
||||
using Microsoft.Extensions.Options;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Models;
|
||||
using YaeBlog.Services;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace YaeBlog.Commands.Binders;
|
||||
|
||||
public sealed class EssayScanServiceBinder : BinderBase<IEssayScanService>
|
||||
{
|
||||
protected override IEssayScanService GetBoundValue(BindingContext bindingContext)
|
||||
{
|
||||
bindingContext.AddService<IEssayScanService>(provider =>
|
||||
{
|
||||
DeserializerBuilder deserializerBuilder = new();
|
||||
deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||
deserializerBuilder.IgnoreUnmatchedProperties();
|
||||
|
||||
SerializerBuilder serializerBuilder = new();
|
||||
serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||
|
||||
IOptions<BlogOptions> options = provider.GetRequiredService<IOptions<BlogOptions>>();
|
||||
ILogger<EssayScanService> logger = provider.GetRequiredService<ILogger<EssayScanService>>();
|
||||
|
||||
return new EssayScanService(serializerBuilder.Build(), deserializerBuilder.Build(), options, logger);
|
||||
});
|
||||
|
||||
return bindingContext.GetRequiredService<IEssayScanService>();
|
||||
}
|
||||
}
|
||||
21
YaeBlog/Commands/Binders/ImageCompressServiceBinder.cs
Normal file
21
YaeBlog/Commands/Binders/ImageCompressServiceBinder.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.CommandLine.Binding;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Services;
|
||||
|
||||
namespace YaeBlog.Commands.Binders;
|
||||
|
||||
public sealed class ImageCompressServiceBinder : BinderBase<ImageCompressService>
|
||||
{
|
||||
protected override ImageCompressService GetBoundValue(BindingContext bindingContext)
|
||||
{
|
||||
bindingContext.AddService(provider =>
|
||||
{
|
||||
IEssayScanService essayScanService = provider.GetRequiredService<IEssayScanService>();
|
||||
ILogger<ImageCompressService> logger = provider.GetRequiredService<ILogger<ImageCompressService>>();
|
||||
|
||||
return new ImageCompressService(essayScanService, logger);
|
||||
});
|
||||
|
||||
return bindingContext.GetRequiredService<ImageCompressService>();
|
||||
}
|
||||
}
|
||||
18
YaeBlog/Commands/Binders/LoggerBinder.cs
Normal file
18
YaeBlog/Commands/Binders/LoggerBinder.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.CommandLine.Binding;
|
||||
|
||||
namespace YaeBlog.Commands.Binders;
|
||||
|
||||
public sealed class LoggerBinder<T> : BinderBase<ILogger<T>>
|
||||
{
|
||||
protected override ILogger<T> GetBoundValue(BindingContext bindingContext)
|
||||
{
|
||||
bindingContext.AddService(_ => LoggerFactory.Create(builder => builder.AddConsole()));
|
||||
bindingContext.AddService<ILogger<T>>(provider =>
|
||||
{
|
||||
ILoggerFactory factory = provider.GetRequiredService<ILoggerFactory>();
|
||||
return factory.CreateLogger<T>();
|
||||
});
|
||||
|
||||
return bindingContext.GetRequiredService<ILogger<T>>();
|
||||
}
|
||||
}
|
||||
263
YaeBlog/Commands/YaeBlogCommand.cs
Normal file
263
YaeBlog/Commands/YaeBlogCommand.cs
Normal file
@@ -0,0 +1,263 @@
|
||||
using System.CommandLine;
|
||||
using Microsoft.Extensions.Options;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Commands.Binders;
|
||||
using YaeBlog.Components;
|
||||
using YaeBlog.Extensions;
|
||||
using YaeBlog.Models;
|
||||
using YaeBlog.Services;
|
||||
|
||||
namespace YaeBlog.Commands;
|
||||
|
||||
public sealed class YaeBlogCommand
|
||||
{
|
||||
private readonly RootCommand _rootCommand = new("YaeBlog Cli");
|
||||
|
||||
public YaeBlogCommand()
|
||||
{
|
||||
AddServeCommand(_rootCommand);
|
||||
AddWatchCommand(_rootCommand);
|
||||
AddListCommand(_rootCommand);
|
||||
AddNewCommand(_rootCommand);
|
||||
AddPublishCommand(_rootCommand);
|
||||
AddScanCommand(_rootCommand);
|
||||
AddCompressCommand(_rootCommand);
|
||||
}
|
||||
|
||||
public Task<int> RunAsync(string[] args)
|
||||
{
|
||||
return _rootCommand.InvokeAsync(args);
|
||||
}
|
||||
|
||||
private static void AddServeCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command serveCommand = new("serve", "Start http server.");
|
||||
rootCommand.AddCommand(serveCommand);
|
||||
|
||||
serveCommand.SetHandler(async context =>
|
||||
{
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
builder.Services.AddControllers();
|
||||
builder.AddYaeBlog();
|
||||
builder.AddServer();
|
||||
|
||||
WebApplication application = builder.Build();
|
||||
|
||||
application.UseStaticFiles();
|
||||
application.UseAntiforgery();
|
||||
application.UseYaeBlog();
|
||||
|
||||
application.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
application.MapControllers();
|
||||
|
||||
CancellationToken token = context.GetCancellationToken();
|
||||
await application.RunAsync(token);
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddWatchCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command command = new("watch", "Start a blog watcher that re-render when file changes.");
|
||||
rootCommand.AddCommand(command);
|
||||
|
||||
command.SetHandler(async context =>
|
||||
{
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder();
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
builder.Services.AddControllers();
|
||||
builder.AddYaeBlog();
|
||||
builder.AddWatcher();
|
||||
|
||||
WebApplication application = builder.Build();
|
||||
|
||||
application.UseStaticFiles();
|
||||
application.UseAntiforgery();
|
||||
application.UseYaeBlog();
|
||||
|
||||
application.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
application.MapControllers();
|
||||
|
||||
CancellationToken token = context.GetCancellationToken();
|
||||
await application.RunAsync(token);
|
||||
});
|
||||
}
|
||||
|
||||
private static void AddNewCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command newCommand = new("new", "Create a new blog file and image directory.");
|
||||
rootCommand.AddCommand(newCommand);
|
||||
|
||||
Argument<string> filenameArgument = new(name: "blog name", description: "The created blog filename.");
|
||||
newCommand.AddArgument(filenameArgument);
|
||||
|
||||
newCommand.SetHandler(async (file, blogOption, _, essayScanService) =>
|
||||
{
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
|
||||
if (contents.Posts.Any(content => content.BlogName == file))
|
||||
{
|
||||
Console.WriteLine("There exists the same title blog in posts.");
|
||||
return;
|
||||
}
|
||||
|
||||
await essayScanService.SaveBlogContent(new BlogContent(
|
||||
new FileInfo(Path.Combine(blogOption.Value.Root, "drafts", file + ".md")),
|
||||
new MarkdownMetadata { Title = file, Date = DateTime.Now },
|
||||
string.Empty, true, [], []));
|
||||
|
||||
Console.WriteLine($"Created new blog '{file}.");
|
||||
}, filenameArgument, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(),
|
||||
new EssayScanServiceBinder());
|
||||
}
|
||||
|
||||
private static void AddListCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command command = new("list", "List all blogs");
|
||||
rootCommand.AddCommand(command);
|
||||
|
||||
command.SetHandler(async (_, _, essyScanService) =>
|
||||
{
|
||||
BlogContents contents = await essyScanService.ScanContents();
|
||||
|
||||
Console.WriteLine($"All {contents.Posts.Count} Posts:");
|
||||
foreach (BlogContent content in contents.Posts.OrderBy(x => x.BlogName))
|
||||
{
|
||||
Console.WriteLine($" - {content.BlogName}");
|
||||
}
|
||||
|
||||
Console.WriteLine($"All {contents.Drafts.Count} Drafts:");
|
||||
foreach (BlogContent content in contents.Drafts.OrderBy(x => x.BlogName))
|
||||
{
|
||||
Console.WriteLine($" - {content.BlogName}");
|
||||
}
|
||||
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder());
|
||||
}
|
||||
|
||||
private static void AddScanCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command command = new("scan", "Scan unused and not found images.");
|
||||
rootCommand.AddCommand(command);
|
||||
|
||||
Option<bool> removeOption =
|
||||
new(name: "--rm", description: "Remove unused images.", getDefaultValue: () => false);
|
||||
command.AddOption(removeOption);
|
||||
|
||||
command.SetHandler(async (_, _, essayScanService, removeOptionValue) =>
|
||||
{
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
List<BlogImageInfo> unusedImages = (from content in contents
|
||||
from image in content.Images
|
||||
where image is { IsUsed: false }
|
||||
select image).ToList();
|
||||
|
||||
if (unusedImages.Count != 0)
|
||||
{
|
||||
Console.WriteLine("Found unused images:");
|
||||
Console.WriteLine("HINT: use '--rm' to remove unused images.");
|
||||
}
|
||||
|
||||
foreach (BlogImageInfo image in unusedImages)
|
||||
{
|
||||
Console.WriteLine($" - {image.File.FullName}");
|
||||
}
|
||||
|
||||
if (removeOptionValue)
|
||||
{
|
||||
foreach (BlogImageInfo image in unusedImages)
|
||||
{
|
||||
image.File.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Used not existed images:");
|
||||
|
||||
foreach (BlogContent content in contents)
|
||||
{
|
||||
foreach (FileInfo file in content.NotfoundImages)
|
||||
{
|
||||
Console.WriteLine($"- {file.Name} in {content.BlogName}");
|
||||
}
|
||||
}
|
||||
}, new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), removeOption);
|
||||
}
|
||||
|
||||
private static void AddPublishCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command command = new("publish", "Publish a new blog file.");
|
||||
rootCommand.AddCommand(command);
|
||||
|
||||
Argument<string> filenameArgument = new(name: "blog name", description: "The published blog filename.");
|
||||
command.AddArgument(filenameArgument);
|
||||
|
||||
command.SetHandler(async (blogOptions, _, essayScanService, filename) =>
|
||||
{
|
||||
BlogContents contents = await essayScanService.ScanContents();
|
||||
|
||||
BlogContent? content = (from blog in contents.Drafts
|
||||
where blog.BlogName == filename
|
||||
select blog).FirstOrDefault();
|
||||
|
||||
if (content is null)
|
||||
{
|
||||
Console.WriteLine("Target blog does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置发布的时间
|
||||
content.Metadata.Date = DateTime.Now;
|
||||
|
||||
// 将选中的博客文件复制到posts
|
||||
await essayScanService.SaveBlogContent(content, isDraft: false);
|
||||
|
||||
// 复制图片文件夹
|
||||
DirectoryInfo sourceImageDirectory =
|
||||
new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName));
|
||||
DirectoryInfo targetImageDirectory =
|
||||
new(Path.Combine(blogOptions.Value.Root, "posts", content.BlogName));
|
||||
|
||||
if (sourceImageDirectory.Exists)
|
||||
{
|
||||
targetImageDirectory.Create();
|
||||
foreach (FileInfo file in sourceImageDirectory.EnumerateFiles())
|
||||
{
|
||||
file.CopyTo(Path.Combine(targetImageDirectory.FullName, file.Name), true);
|
||||
}
|
||||
|
||||
sourceImageDirectory.Delete(true);
|
||||
}
|
||||
|
||||
// 删除原始的文件
|
||||
FileInfo sourceBlogFile = new(Path.Combine(blogOptions.Value.Root, "drafts", content.BlogName + ".md"));
|
||||
sourceBlogFile.Delete();
|
||||
}, new BlogOptionsBinder(),
|
||||
new LoggerBinder<EssayScanService>(), new EssayScanServiceBinder(), filenameArgument);
|
||||
}
|
||||
|
||||
private static void AddCompressCommand(RootCommand rootCommand)
|
||||
{
|
||||
Command command = new("compress", "Compress png/jpeg image to webp image to reduce size.");
|
||||
rootCommand.Add(command);
|
||||
|
||||
Option<bool> dryRunOption = new("--dry-run", description: "Dry run the compression task but not write.",
|
||||
getDefaultValue: () => false);
|
||||
command.AddOption(dryRunOption);
|
||||
|
||||
command.SetHandler(ImageCommandHandler,
|
||||
new BlogOptionsBinder(), new LoggerBinder<EssayScanService>(), new LoggerBinder<ImageCompressService>(),
|
||||
new EssayScanServiceBinder(), new ImageCompressServiceBinder(), dryRunOption);
|
||||
}
|
||||
|
||||
private static async Task ImageCommandHandler(IOptions<BlogOptions> _, ILogger<EssayScanService> _1,
|
||||
ILogger<ImageCompressService> _2,
|
||||
IEssayScanService _3, ImageCompressService imageCompressService, bool dryRun)
|
||||
{
|
||||
await imageCompressService.Compress(dryRun);
|
||||
}
|
||||
}
|
||||
20
YaeBlog/Components/App.razor
Normal file
20
YaeBlog/Components/App.razor
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<base href="/"/>
|
||||
<link rel="stylesheet" href="YaeBlog.styles.css"/>
|
||||
<link rel="icon" href="images/favicon.ico"/>
|
||||
<link rel="stylesheet" href="globals.css"/>
|
||||
<link rel="stylesheet" href="tailwind.g.css"/>
|
||||
<HeadOutlet/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes/>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,9 +1,8 @@
|
||||
@using Microsoft.Extensions.Options
|
||||
@using YaeBlog.Abstraction
|
||||
@using YaeBlog.Models
|
||||
|
||||
@inject IEssayContentService Contents
|
||||
@inject IOptions<BlogOptions> Options
|
||||
@inject BlogOptions Options
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="p-10">
|
||||
@@ -44,7 +43,7 @@
|
||||
|
||||
<div class="px-6">
|
||||
<p class="text-lg">
|
||||
@(Options.Value.Announcement)
|
||||
@(Options.Announcement)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<div class="flex flex-col p-3">
|
||||
<div class="text-3xl font-bold py-2">
|
||||
<a href="@(Essay.EssayLink)">@(Essay.Title)</a>
|
||||
<a href="/blog/essays/@(Essay.FileName)" target="_blank">@(Essay.Title)</a>
|
||||
</div>
|
||||
|
||||
<div class="p-2 flex flex-row justify-content-start gap-2">
|
||||
@@ -14,7 +14,9 @@
|
||||
@foreach (string key in Essay.Tags)
|
||||
{
|
||||
<div class="text-sky-600">
|
||||
<Anchor Address="@($"/blog/tags/?tagName={UrlEncoder.Default.Encode(key)}")" Text="@($"# {key}")"/>
|
||||
<a href="/blog/tags/?tagName=@(UrlEncoder.Default.Encode(key))">
|
||||
# @key
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
22
YaeBlog/Components/Foonter.razor
Normal file
22
YaeBlog/Components/Foonter.razor
Normal file
@@ -0,0 +1,22 @@
|
||||
<div class="flex flex-col text-center py-2">
|
||||
<div>
|
||||
<p class="text-md">
|
||||
2021 - @(DateTimeOffset.Now.Year) ©
|
||||
<Anchor Address="https://rrricardot.top" Text="Ricardo Ren"/>
|
||||
,由
|
||||
<Anchor Address="https://dotnet.microsoft.com" Text="@DotnetVersion"/>
|
||||
驱动。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-md">
|
||||
<a href="https://beian.miit.gov.cn" target="_blank" class="text-black">蜀ICP备2022004429号-1</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private string DotnetVersion => $".NET {Environment.Version}";
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
@using YaeBlog.Models
|
||||
@inject BlogOptions Options
|
||||
|
||||
<div class="px-4 py-8 border border-sky-700 rounded-md bg-sky-200">
|
||||
<div class="flex flex-col gap-3 text-md">
|
||||
<div>
|
||||
文章作者:<a href="https://rrricardo.top" target="_blank" class="text-blue-600">初冬的朝阳</a>
|
||||
文章作者:<a href="https://rrricardo.top" target="_blank" class="text-blue-600">Ricardo Ren</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -18,20 +21,9 @@
|
||||
</a>
|
||||
许可协议,诸位读者如有兴趣可任意转载,不必征询许可,但请注明“转载自
|
||||
<a href="https://rrricardo.top/blog/" target="_blank" class="text-blue-600">
|
||||
Jackfiled's Blog
|
||||
Ricardo's Blog
|
||||
</a>”。
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex justify-center">
|
||||
<p>如果觉得不错的话,可以支持一下作者哦~</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center">
|
||||
<AppreciationCode/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
8
YaeBlog/Dockerfile
Normal file
8
YaeBlog/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
||||
|
||||
WORKDIR /app
|
||||
COPY bin/Release/net9.0/publish/ ./
|
||||
COPY source/ ./source/
|
||||
COPY appsettings.json .
|
||||
|
||||
ENTRYPOINT ["dotnet", "YaeBlog.dll", "serve"]
|
||||
33
YaeBlog/Extensions/ServiceCollectionExtensions.cs
Normal file
33
YaeBlog/Extensions/ServiceCollectionExtensions.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Markdig;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
namespace YaeBlog.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddMarkdig(this IServiceCollection collection)
|
||||
{
|
||||
MarkdownPipelineBuilder builder = new();
|
||||
|
||||
builder.UseAdvancedExtensions();
|
||||
|
||||
collection.AddSingleton<MarkdownPipeline>(_ => builder.Build());
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddYamlParser(this IServiceCollection collection)
|
||||
{
|
||||
DeserializerBuilder deserializerBuilder = new();
|
||||
deserializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||
deserializerBuilder.IgnoreUnmatchedProperties();
|
||||
collection.AddSingleton(deserializerBuilder.Build());
|
||||
|
||||
SerializerBuilder serializerBuilder = new();
|
||||
serializerBuilder.WithNamingConvention(CamelCaseNamingConvention.Instance);
|
||||
collection.AddSingleton(serializerBuilder.Build());
|
||||
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
47
YaeBlog/Extensions/WebApplicationBuilderExtensions.cs
Normal file
47
YaeBlog/Extensions/WebApplicationBuilderExtensions.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using AngleSharp;
|
||||
using Microsoft.Extensions.Options;
|
||||
using YaeBlog.Abstraction;
|
||||
using YaeBlog.Services;
|
||||
using YaeBlog.Models;
|
||||
using YaeBlog.Processors;
|
||||
|
||||
namespace YaeBlog.Extensions;
|
||||
|
||||
public static class WebApplicationBuilderExtensions
|
||||
{
|
||||
public static WebApplicationBuilder AddYaeBlog(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.Configure<BlogOptions>(builder.Configuration.GetSection(BlogOptions.OptionName));
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
|
||||
builder.Services.AddMarkdig();
|
||||
builder.Services.AddYamlParser();
|
||||
builder.Services.AddSingleton<AngleSharp.IConfiguration>(_ => Configuration.Default);
|
||||
builder.Services.AddSingleton<IEssayScanService, EssayScanService>();
|
||||
builder.Services.AddSingleton<RendererService>();
|
||||
builder.Services.AddSingleton<IEssayContentService, EssayContentService>();
|
||||
builder.Services.AddTransient<ImagePostRenderProcessor>();
|
||||
builder.Services.AddTransient<HeadlinePostRenderProcessor>();
|
||||
builder.Services.AddTransient<EssayStylesPostRenderProcessor>();
|
||||
builder.Services.AddTransient<BlogOptions>(provider =>
|
||||
provider.GetRequiredService<IOptions<BlogOptions>>().Value);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder AddServer(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddHostedService<BlogHostedService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder AddWatcher(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddTransient<BlogChangeWatcher>();
|
||||
builder.Services.AddHostedService<BlogHotReloadService>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
44
YaeBlog/Layout/BlogLayout.razor
Normal file
44
YaeBlog/Layout/BlogLayout.razor
Normal file
@@ -0,0 +1,44 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@attribute [StreamRendering]
|
||||
|
||||
<main class="container mx-auto flex flex-col min-h-screen">
|
||||
<div class="grid grid-cols-3 mx-3">
|
||||
<div class="md:col-span-2 col-span-3 h-20 flex items-center">
|
||||
<a href="/blog/">
|
||||
<span class="text-blue-600 text-2xl">Ricardo's Blog</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="md:col-span-1 col-span-3 h-20 flex items-center">
|
||||
<div class="flex flex-row w-full px-2 gap-3 md:justify-center justify-end">
|
||||
<div>
|
||||
<a href="/blog/archives/">
|
||||
<span class="text-xl text-blue-600">归档</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/blog/tags/">
|
||||
<span class="text-xl text-blue-600">标签</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/about/" target="_blank">
|
||||
<span class="text-xl text-blue-600">关于</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/friends/" target="_blank">
|
||||
<span class="text-xl text-blue-600">友链</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-2 flex-grow">
|
||||
@Body
|
||||
</div>
|
||||
|
||||
<Foonter/>
|
||||
</main>
|
||||
@@ -17,11 +17,13 @@
|
||||
|
||||
<Anchor
|
||||
Address="/about/"
|
||||
Text="关于"/>
|
||||
Text="关于"
|
||||
NewPage="@(true)"/>
|
||||
|
||||
<Anchor
|
||||
Address="/friends"
|
||||
Text="友链"/>
|
||||
Text="友链"
|
||||
NewPage="@(true)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
15
YaeBlog/Models/BlogContents.cs
Normal file
15
YaeBlog/Models/BlogContents.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
public record BlogContents(ConcurrentBag<BlogContent> Drafts, ConcurrentBag<BlogContent> Posts)
|
||||
: IEnumerable<BlogContent>
|
||||
{
|
||||
IEnumerator<BlogContent> IEnumerable<BlogContent>.GetEnumerator()
|
||||
{
|
||||
return Posts.Concat(Drafts).GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator() => ((IEnumerable<BlogContent>)this).GetEnumerator();
|
||||
}
|
||||
61
YaeBlog/Models/BlogEssay.cs
Normal file
61
YaeBlog/Models/BlogEssay.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
public class BlogEssay : IComparable<BlogEssay>
|
||||
{
|
||||
public required string Title { get; init; }
|
||||
|
||||
public required string FileName { get; init; }
|
||||
|
||||
public required bool IsDraft { get; init; }
|
||||
|
||||
public required DateTime PublishTime { get; init; }
|
||||
|
||||
public required string Description { get; init; }
|
||||
|
||||
public required uint WordCount { get; init; }
|
||||
|
||||
public required string ReadTime { get; init; }
|
||||
|
||||
public List<string> Tags { get; } = [];
|
||||
|
||||
public required string HtmlContent { get; init; }
|
||||
|
||||
public BlogEssay WithNewHtmlContent(string newHtmlContent)
|
||||
{
|
||||
var essay = new BlogEssay
|
||||
{
|
||||
Title = Title,
|
||||
FileName = FileName,
|
||||
IsDraft = IsDraft,
|
||||
PublishTime = PublishTime,
|
||||
Description = Description,
|
||||
WordCount = WordCount,
|
||||
ReadTime = ReadTime,
|
||||
HtmlContent = newHtmlContent
|
||||
};
|
||||
essay.Tags.AddRange(Tags);
|
||||
|
||||
return essay;
|
||||
}
|
||||
|
||||
public int CompareTo(BlogEssay? other)
|
||||
{
|
||||
if (other is null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 草稿文章应当排在前面
|
||||
if (IsDraft != other.IsDraft)
|
||||
{
|
||||
return IsDraft ? -1 : 1;
|
||||
}
|
||||
|
||||
return other.PublishTime.CompareTo(PublishTime);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Title}-{PublishTime}";
|
||||
}
|
||||
}
|
||||
26
YaeBlog/Models/BlogOptions.cs
Normal file
26
YaeBlog/Models/BlogOptions.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
public class BlogOptions
|
||||
{
|
||||
public const string OptionName = "Blog";
|
||||
|
||||
/// <summary>
|
||||
/// 博客markdown文件的根目录
|
||||
/// </summary>
|
||||
public required string Root { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 博客正文的广而告之
|
||||
/// </summary>
|
||||
public required string Announcement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 博客的起始年份
|
||||
/// </summary>
|
||||
public required int StartYear { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 博客的友链
|
||||
/// </summary>
|
||||
public required List<FriendLink> Links { get; set; }
|
||||
}
|
||||
27
YaeBlog/Models/FriendLink.cs
Normal file
27
YaeBlog/Models/FriendLink.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace YaeBlog.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 友链模型类
|
||||
/// </summary>
|
||||
public class FriendLink
|
||||
{
|
||||
/// <summary>
|
||||
/// 友链名称
|
||||
/// </summary>
|
||||
public required string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 友链的简单介绍
|
||||
/// </summary>
|
||||
public required string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 友链地址
|
||||
/// </summary>
|
||||
public required string Link { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 头像地址
|
||||
/// </summary>
|
||||
public required string AvatarImage { get; set; }
|
||||
}
|
||||
@@ -4,9 +4,7 @@ public class MarkdownMetadata
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Date { get; set; }
|
||||
|
||||
public string? UpdateTime { get; set; }
|
||||
public DateTime? Date { get; set; }
|
||||
|
||||
public List<string>? Tags { get; set; }
|
||||
}
|
||||
79
YaeBlog/Pages/About.razor
Normal file
79
YaeBlog/Pages/About.razor
Normal file
@@ -0,0 +1,79 @@
|
||||
@page "/about"
|
||||
|
||||
<PageTitle>
|
||||
关于
|
||||
</PageTitle>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div>
|
||||
<h1 class="text-4xl">关于</h1>
|
||||
</div>
|
||||
|
||||
<div class="py-4">
|
||||
<span class="italic">把字刻在石头上!(・’ω’・)</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col p-2">
|
||||
<div class="flex flex-col p-2">
|
||||
<div class="pb-2">
|
||||
<h3 class="text-2xl">关于我</h3>
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
计算机科学与技术在读大学生,明光村幼儿园附属大学所属。正处于读书和失业的叠加态。
|
||||
一般在互联网上使用<span class="italic">初冬的朝阳</span>或者<span class="italic">jackfiled</span>的名字活动。
|
||||
<span class="line-through">都是ICP备案过的人了,网名似乎没有太大的用处(</span>
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
主要是一个C#程序员,目前也在尝试写一点Rust。
|
||||
总体上对于编程语言的态度是“<span>大家都是我的翅膀.jpg</span>”。
|
||||
前后端分离的项目本当上手。
|
||||
常常因为现实的压力而写一些C/C++。
|
||||
<span class="line-through">对于Java和Go的评价很低。</span>
|
||||
日常使用ArchLinux。
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
100%社恐。日常生活是宅在电脑前面自言自语。
|
||||
兴趣活动是读书和看番,目前在玩原神和三角洲。
|
||||
</div>
|
||||
|
||||
<div class="py-4">
|
||||
常常被人批评没有梦想,这里就随便瞎编一下。
|
||||
成为嵌入式工程师,修好桌面上的<a href="https://www.bilibili.com/video/BV1VA411p7MD">HoloCubic</a>。
|
||||
完成第一个不是课程设计的个人开源项目。
|
||||
遇到能够搭伙过日子的人也算是一大梦想,虽然社恐人根本不知道从何开始的说,
|
||||
<span class="line-through">什么时候天上才能掉美少女?</span>
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
公开的联系渠道是<a href="mailto:shicangjuner@outlook.com">电子邮件</a>。
|
||||
也可以试试在各大平台搜索上面提到的名字。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col p-2">
|
||||
<div class="pb-2">
|
||||
<h3 class="text-2xl">关于本站</h3>
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
本站肇始于2021年下半年,在开始的两年中个人网站和博客是分别的两个网站,个人网站是裸HTML写的,博客是用
|
||||
<a href="https://hexo.io">Hexo</a>渲染的。
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
2024年,我们决定使用.NET技术完全重构两个网站,合二为一。虽然目前这个版本还是一个半成品,但是我们一定会努力的~(确信。
|
||||
</div>
|
||||
|
||||
<div class="py-2">
|
||||
2025年,我们将使用的样式库从Bootstrap迁移到Tailwind CSS,将现代的前端技术同Blazor结合起来。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@foreach (IGrouping<DateTimeOffset, BlogEssay> group in _essays)
|
||||
@foreach (IGrouping<DateTime, BlogEssay> group in _essays)
|
||||
{
|
||||
<div class="p-2">
|
||||
<div class="flex flex-col">
|
||||
@@ -30,7 +30,7 @@
|
||||
<div class="px-4 py-4 flex flex-col">
|
||||
@foreach (BlogEssay essay in group)
|
||||
{
|
||||
<a href="@($"/blog/essays/{essay.FileName}")">
|
||||
<a target="_blank" href="@($"/blog/essays/{essay.FileName}")">
|
||||
<div class="flex flex-row p-2 mx-1 rounded-lg hover:bg-gray-300">
|
||||
<div class="w-20">
|
||||
@(essay.PublishTime.ToString("MM月dd日"))
|
||||
@@ -51,13 +51,13 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private readonly List<IGrouping<DateTimeOffset, BlogEssay>> _essays = [];
|
||||
private readonly List<IGrouping<DateTime, BlogEssay>> _essays = [];
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
|
||||
_essays.AddRange(from essay in Contents.Essays
|
||||
group essay by new DateTimeOffset(essay.PublishTime.Year, 1, 1,0, 0, 0, TimeSpan.Zero));
|
||||
group essay by new DateTime(essay.PublishTime.Year, 1, 1));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
@inject NavigationManager NavigationInstance
|
||||
|
||||
<PageTitle>
|
||||
Jackfiled's Blog
|
||||
Ricardo's Blog
|
||||
</PageTitle>
|
||||
|
||||
<div>
|
||||
@@ -39,11 +39,6 @@
|
||||
{
|
||||
_page = Page ?? 1;
|
||||
_pageCount = Contents.Count / EssaysPerPage + 1;
|
||||
(_pageCount, int reminder) = int.DivRem(Contents.Count, EssaysPerPage);
|
||||
if (reminder > 0)
|
||||
{
|
||||
_pageCount += 1;
|
||||
}
|
||||
|
||||
if (EssaysPerPage * _page > Contents.Count + EssaysPerPage)
|
||||
{
|
||||
@@ -12,42 +12,47 @@
|
||||
|
||||
<div class="flex flex-col py-8">
|
||||
<div>
|
||||
<div class="flex flex-col items-center">
|
||||
<div>
|
||||
<h1 id="title" class="text-4xl">@(_essay!.Title)</h1>
|
||||
<h1 id="title" class="text-4xl">@(_essay!.Title)</h1>
|
||||
<div class="col-auto">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-6 pt-4 pb-2">
|
||||
<div class="flex flex-row gap-4">
|
||||
<div class="font-light">
|
||||
@(_essay!.PublishTime.ToString("yyyy-MM-dd"))
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-4 py-2">
|
||||
@foreach (string tag in _essay!.Tags)
|
||||
{
|
||||
<div class="text-sky-500">
|
||||
<a href="/blog/tags/?tagName=@(UrlEncoder.Default.Encode(tag))">
|
||||
# @(tag)
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="font-light pb-1">
|
||||
发布于: @(_essay.PublishTime.ToString("yyyy年MM月dd日 HH:mm:ss"))
|
||||
</div>
|
||||
|
||||
@if (_essay.UpdateTime != _essay.PublishTime)
|
||||
@foreach (string tag in _essay!.Tags)
|
||||
{
|
||||
<div class="font-light pb-1">
|
||||
更新于: @(_essay.UpdateTime.ToString("yyyy年MM月dd日 HH:mm:ss"))
|
||||
<div class="text-sky-500">
|
||||
<a href="/blog/tags/?tagName=@(UrlEncoder.Default.Encode(tag))">
|
||||
# @(tag)
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="font-light pb-1">
|
||||
总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)
|
||||
</div>
|
||||
<div class="px-6 pt-2 pb-4">
|
||||
<div class="font-light">
|
||||
总字数:@(_essay!.WordCount)字,预计阅读时间 @(_essay!.ReadTime)。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3">
|
||||
<div class="col-span-3 md:col-span-2 flex flex-col gap-3">
|
||||
<div>
|
||||
@((MarkupString)_essay!.HtmlContent)
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LicenseDisclaimer EssayFilename="@BlogKey"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-3 md:col-span-1">
|
||||
<div class="flex flex-col sticky top-20 px-8 pt-20">
|
||||
<div class="flex flex-col sticky top-0 px-8">
|
||||
<div>
|
||||
<h3 class="text-2xl">文章目录</h3>
|
||||
</div>
|
||||
@@ -88,17 +93,8 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-3 md:col-span-2 flex flex-col gap-3">
|
||||
<div>
|
||||
@((MarkupString)_essay!.HtmlContent)
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<LicenseDisclaimer EssayFilename="@BlogKey"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@code {
|
||||
@@ -1,7 +1,6 @@
|
||||
@page "/friends"
|
||||
@using Microsoft.Extensions.Options
|
||||
@using YaeBlog.Models
|
||||
@inject IOptions<BlogOptions> BlogOptionInstance
|
||||
@inject BlogOptions Options
|
||||
|
||||
<PageTitle>
|
||||
友链
|
||||
@@ -19,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 g-4 p-2">
|
||||
@foreach (FriendLink link in BlogOptionInstance.Value.Links.Where(i => i is not null).Select(i => i!))
|
||||
@foreach (FriendLink link in Options.Links)
|
||||
{
|
||||
<div>
|
||||
<a href="@(link.Link)" target="_blank" class="mx-5">
|
||||
@@ -44,3 +43,7 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
59
YaeBlog/Pages/Index.razor
Normal file
59
YaeBlog/Pages/Index.razor
Normal file
@@ -0,0 +1,59 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>
|
||||
Ricardo's Index
|
||||
</PageTitle>
|
||||
|
||||
<div class="mx-20">
|
||||
<div class="grid grid-cols-3 py-4">
|
||||
<div class="col-span-3 md:col-span-1 p-5 p-lg-0">
|
||||
<img src="images/avatar.png" alt="Ricardo's Avatar" class="h-auto max-w-full">
|
||||
</div>
|
||||
|
||||
<div class="col-span-3 md:col-span-2">
|
||||
<div class="flex flex-col px-3 gap-y-3">
|
||||
<div class="">
|
||||
<div class="text-3xl font-bold">初冬的朝阳 (Ricardo Ren)</div>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="text-lg">a.k.a jackfiled</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="text-lg italic">世界很大,时间很长。</p>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<p class="text-lg">
|
||||
平平无奇的计算机科学与技术学徒,连微小的贡献都没做。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="py-5">
|
||||
<p class="text-lg">恕我不能亲自为您沏茶(?),还是非常欢迎您能够来到我的主页。</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="text-lg py-1">
|
||||
如果您想四处看看,了解一下屏幕对面的人,可以在我的 <Anchor Address="/blog/" Text="博客"/> 看看。
|
||||
如果您对于明光村幼儿园某附属技校的计算机教学感兴趣,您可以移步到
|
||||
<Anchor Address="https://jackfiled.github.io/wiki/" Text="我的学习笔记"/>,
|
||||
<span class="fs-5 text-decoration-line-through">虽然这笔记我自己也木有看过。</span>
|
||||
如果您想批判一下我的代码,在
|
||||
<Anchor Address="https://github.com/jackfiled/" Text="Github"/> 和
|
||||
<Anchor Address="https://git.rrricardo.top/jackfiled/" Text="Gitea"/>
|
||||
都可以找到。
|
||||
</p>
|
||||
<p class="text-lg py-1">
|
||||
如果您真的很闲,也可以四处搜寻一下,也许存在着一些不为人知的彩蛋。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -16,14 +16,15 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
|
||||
public async Task<BlogEssay> ProcessAsync(BlogEssay essay)
|
||||
{
|
||||
BrowsingContext context = new(Configuration.Default);
|
||||
IDocument document = await context.OpenAsync(req => req.Content(essay.HtmlContent));
|
||||
IDocument document = await context.OpenAsync(
|
||||
req => req.Content(essay.HtmlContent));
|
||||
|
||||
ApplyGlobalCssStyles(document);
|
||||
BeatifyTable(document);
|
||||
BeatifyList(document);
|
||||
BeatifyInlineCode(document);
|
||||
|
||||
return essay with { HtmlContent = document.DocumentElement.OuterHtml };
|
||||
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, string> _globalCssStyles = new()
|
||||
@@ -35,7 +36,6 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
|
||||
{ "h5", "text-lg font-bold py-1" },
|
||||
{ "p", "p-2" },
|
||||
{ "img", "w-11/12 block mx-auto my-2 rounded-md shadow-md" },
|
||||
{ "a", "text-blue-600" }
|
||||
};
|
||||
|
||||
private void ApplyGlobalCssStyles(IDocument document)
|
||||
@@ -102,33 +102,17 @@ public sealed class EssayStylesPostRenderProcessor : IPostRenderProcessor
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 美化各种列表元素
|
||||
/// </summary>
|
||||
/// <param name="document"></param>
|
||||
private static void BeatifyList(IDocument document)
|
||||
{
|
||||
foreach (IElement listElement in from e in document.All
|
||||
where e.LocalName is "ol" or "ul"
|
||||
foreach (IElement ulElement in from e in document.All
|
||||
where e.LocalName == "ul"
|
||||
select e)
|
||||
{
|
||||
// 给有序或者无序列表添加不同的样式
|
||||
listElement.ClassList.Add("ml-10");
|
||||
switch (listElement.LocalName)
|
||||
{
|
||||
case "ul":
|
||||
{
|
||||
listElement.ClassList.Add("list-disc");
|
||||
break;
|
||||
}
|
||||
case "ol":
|
||||
{
|
||||
listElement.ClassList.Add("list-decimal");
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 首先给<ul>元素添加样式
|
||||
ulElement.ClassList.Add("list-disc ml-10");
|
||||
|
||||
foreach (IElement liElement in from e in listElement.Children
|
||||
|
||||
foreach (IElement liElement in from e in ulElement.Children
|
||||
where e.LocalName == "li"
|
||||
select e)
|
||||
{
|
||||
@@ -67,7 +67,7 @@ public class HeadlinePostRenderProcessor(
|
||||
logger.LogWarning("Failed to add headline of {}.", essay.FileName);
|
||||
}
|
||||
|
||||
return essay with { HtmlContent = document.DocumentElement.OuterHtml };
|
||||
return essay.WithNewHtmlContent(document.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
private static BlogHeadline ParserHeadlineElement(IElement element)
|
||||
@@ -7,12 +7,6 @@ using YaeBlog.Models;
|
||||
|
||||
namespace YaeBlog.Processors;
|
||||
|
||||
/// <summary>
|
||||
/// 图片地址路径后处理器
|
||||
/// 将本地图片地址修改为图片API地址
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="options"></param>
|
||||
public class ImagePostRenderProcessor(
|
||||
ILogger<ImagePostRenderProcessor> logger,
|
||||
IOptions<BlogOptions> options)
|
||||
@@ -40,7 +34,7 @@ public class ImagePostRenderProcessor(
|
||||
}
|
||||
}
|
||||
|
||||
return essay with { HtmlContent = html.DocumentElement.OuterHtml };
|
||||
return essay.WithNewHtmlContent(html.DocumentElement.OuterHtml);
|
||||
}
|
||||
|
||||
public string Name => nameof(ImagePostRenderProcessor);
|
||||
@@ -65,7 +59,7 @@ public class ImagePostRenderProcessor(
|
||||
}
|
||||
|
||||
string imageLink = "api/files/" + filename;
|
||||
logger.LogDebug("Generate image link '{link}' for image file '{filename}'.",
|
||||
logger.LogDebug("Generate image link '{}' for image file '{}'.",
|
||||
imageLink, filename);
|
||||
|
||||
return imageLink;
|
||||
4
YaeBlog/Program.cs
Normal file
4
YaeBlog/Program.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
using YaeBlog.Commands;
|
||||
|
||||
YaeBlogCommand command = new();
|
||||
await command.RunAsync(args);
|
||||
@@ -16,11 +16,11 @@ public sealed class BlogHotReloadService(
|
||||
|
||||
await rendererService.RenderAsync(true);
|
||||
|
||||
Task[] reloadTasks = [WatchFileAsync(stoppingToken)];
|
||||
Task[] reloadTasks = [FileWatchTask(stoppingToken)];
|
||||
await Task.WhenAll(reloadTasks);
|
||||
}
|
||||
|
||||
private async Task WatchFileAsync(CancellationToken token)
|
||||
private async Task FileWatchTask(CancellationToken token)
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
@@ -33,15 +33,6 @@ public sealed class BlogHotReloadService(
|
||||
break;
|
||||
}
|
||||
|
||||
FileInfo changeFileInfo = new(changeFile);
|
||||
|
||||
if (changeFileInfo.Name.StartsWith('.'))
|
||||
{
|
||||
// Ignore dot-started file and directory.
|
||||
logger.LogDebug("Ignore hidden file: {}.", changeFile);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.LogInformation("{} changed, re-rendering.", changeFile);
|
||||
essayContentService.Clear();
|
||||
await rendererService.RenderAsync(true);
|
||||
@@ -109,12 +109,6 @@ public partial class EssayScanService : IEssayScanService
|
||||
{
|
||||
foreach (BlogResult blog in fileContents)
|
||||
{
|
||||
if (blog.BlogContent.Length < 4)
|
||||
{
|
||||
// Even not contains a legal header.
|
||||
continue;
|
||||
}
|
||||
|
||||
int endPos = blog.BlogContent.IndexOf("---", 4, StringComparison.Ordinal);
|
||||
if (!blog.BlogContent.StartsWith("---") || endPos is -1 or 0)
|
||||
{
|
||||
@@ -127,15 +121,14 @@ public partial class EssayScanService : IEssayScanService
|
||||
try
|
||||
{
|
||||
MarkdownMetadata metadata = _yamlDeserializer.Deserialize<MarkdownMetadata>(metadataString);
|
||||
_logger.LogDebug("Scan metadata title: '{title}' for {name}.", metadata.Title, blog.BlogFile.Name);
|
||||
_logger.LogDebug("Scan metadata title: '{}' for {}.", metadata.Title, blog.BlogFile.Name);
|
||||
|
||||
contents.Add(new BlogContent(blog.BlogFile, metadata, blog.BlogContent[(endPos + 3)..], isDraft,
|
||||
blog.Images, blog.NotFoundImages));
|
||||
}
|
||||
catch (YamlException e)
|
||||
{
|
||||
_logger.LogWarning("Failed to parser metadata from {name} due to {exception}, skipping",
|
||||
blog.BlogFile.Name, e);
|
||||
_logger.LogWarning("Failed to parser metadata from {} due to {}, skipping", blog.BlogFile.Name, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -147,6 +140,7 @@ public partial class EssayScanService : IEssayScanService
|
||||
|
||||
private async Task<ImageResult> ScanImagePreBlog(DirectoryInfo directory, string blogName, string content)
|
||||
{
|
||||
MatchCollection matchResult = ImagePattern.Matches(content);
|
||||
DirectoryInfo imageDirectory = new(Path.Combine(directory.FullName, blogName));
|
||||
|
||||
Dictionary<string, bool> usedImages = imageDirectory.Exists
|
||||
@@ -154,15 +148,10 @@ public partial class EssayScanService : IEssayScanService
|
||||
: [];
|
||||
List<FileInfo> notFoundImages = [];
|
||||
|
||||
// 同时扫描markdown格式和HTML格式的图片
|
||||
MatchCollection markdownMatchResult = MarkdownImagePattern.Matches(content);
|
||||
MatchCollection htmlMatchResult = HtmlImagePattern.Matches(content);
|
||||
|
||||
IEnumerable<string> imageNames = from match in markdownMatchResult.Concat(htmlMatchResult)
|
||||
select match.Groups[1].Value;
|
||||
|
||||
foreach (string imageName in imageNames)
|
||||
foreach (Match match in matchResult)
|
||||
{
|
||||
string imageName = match.Groups[1].Value;
|
||||
|
||||
// 判断md文件中的图片名称中是否包含文件夹名称
|
||||
// 例如 blog-1/image.png 或者 image.png
|
||||
// 如果不带文件夹名称
|
||||
@@ -209,10 +198,7 @@ public partial class EssayScanService : IEssayScanService
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\!\[.*?\]\((.*?)\)")]
|
||||
private static partial Regex MarkdownImagePattern { get; }
|
||||
|
||||
[GeneratedRegex("""<img\s[^>]*?src\s*=\s*["']([^"']*)["'][^>]*>""")]
|
||||
private static partial Regex HtmlImagePattern { get; }
|
||||
private static partial Regex ImagePattern { get; }
|
||||
|
||||
|
||||
private DirectoryInfo ValidateRootDirectory()
|
||||
@@ -56,7 +56,7 @@ public sealed class ImageCompressService(IEssayScanService essayScanService, ILo
|
||||
}
|
||||
|
||||
CompressResult[] compressedImages = (await Task.WhenAll(from image in uncompressedImages
|
||||
select Task.Run(async () => new CompressResult(image, await ConvertToWebp(image))))).ToArray();
|
||||
select Task.Run(async () => new CompressResult(image, await ConvertToWebp(image.Content))))).ToArray();
|
||||
|
||||
compressedSize += compressedImages.Select(i => i.CompressContent.Length).Sum();
|
||||
|
||||
@@ -65,8 +65,7 @@ public sealed class ImageCompressService(IEssayScanService essayScanService, ILo
|
||||
select r.ImageInfo with
|
||||
{
|
||||
File = new FileInfo(r.ImageInfo.File.FullName.Split('.')[0] + ".webp"),
|
||||
Content = r.CompressContent,
|
||||
MineType = "image/webp"
|
||||
Content = r.CompressContent
|
||||
}).ToList();
|
||||
// 修改文本
|
||||
string blogContent = compressedImages.Aggregate(content.Content, (c, r) =>
|
||||
@@ -89,31 +88,21 @@ public sealed class ImageCompressService(IEssayScanService essayScanService, ILo
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> ConvertToWebp(BlogImageInfo image)
|
||||
private static async Task<byte[]> ConvertToWebp(byte[] image)
|
||||
{
|
||||
using ImageJob job = new();
|
||||
BuildJobResult result = await job.Decode(MemorySource.Borrow(image.Content))
|
||||
.Branch(f => f.EncodeToBytes(new WebPLosslessEncoder()))
|
||||
BuildJobResult result = await job.Decode(MemorySource.Borrow(image))
|
||||
.EncodeToBytes(new WebPLossyEncoder(75))
|
||||
.Finish()
|
||||
.InProcessAsync();
|
||||
|
||||
// 超过128KB的图片使用有损压缩
|
||||
// 反之使用无损压缩
|
||||
ArraySegment<byte>? array = result.First?.TryGetBytes();
|
||||
|
||||
ArraySegment<byte>? losslessImage = result.TryGet(1)?.TryGetBytes();
|
||||
ArraySegment<byte>? lossyImage = result.TryGet(2)?.TryGetBytes();
|
||||
|
||||
if (image.Size <= 128 * 1024 && losslessImage.HasValue)
|
||||
if (array.HasValue)
|
||||
{
|
||||
return losslessImage.Value.ToArray();
|
||||
return array.Value.ToArray();
|
||||
}
|
||||
|
||||
if (lossyImage.HasValue)
|
||||
{
|
||||
return lossyImage.Value.ToArray();
|
||||
}
|
||||
|
||||
throw new BlogCommandException($"Failed to convert {image.File.Name} to webp format: return value is null.");
|
||||
throw new BlogFileException();
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ using YaeBlog.Models;
|
||||
|
||||
namespace YaeBlog.Services;
|
||||
|
||||
public sealed partial class RendererService(
|
||||
public partial class RendererService(
|
||||
ILogger<RendererService> logger,
|
||||
IEssayScanService essayScanService,
|
||||
MarkdownPipeline markdownPipeline,
|
||||
@@ -34,32 +34,42 @@ public sealed partial class RendererService(
|
||||
}
|
||||
|
||||
IEnumerable<BlogContent> preProcessedContents = await PreProcess(posts);
|
||||
ConcurrentBag<BlogEssay> essays = [];
|
||||
|
||||
Parallel.ForEach(preProcessedContents, content =>
|
||||
List<BlogEssay> essays = [];
|
||||
foreach (BlogContent content in preProcessedContents)
|
||||
{
|
||||
(uint wordCount, string readTime) = GetWordCount(content);
|
||||
DateTimeOffset publishDate = content.Metadata.Date is null
|
||||
? DateTimeOffset.Now
|
||||
: DateTimeOffset.Parse(content.Metadata.Date);
|
||||
// 如果不存在最后的更新时间,就把更新时间设置为发布时间
|
||||
DateTimeOffset updateTime = content.Metadata.UpdateTime is null
|
||||
? publishDate
|
||||
: DateTimeOffset.Parse(content.Metadata.UpdateTime);
|
||||
string description = GetDescription(content);
|
||||
List<string> tags = content.Metadata.Tags ?? [];
|
||||
uint wordCount = GetWordCount(content);
|
||||
BlogEssay essay = new()
|
||||
{
|
||||
Title = content.Metadata.Title ?? content.BlogName,
|
||||
FileName = content.BlogName,
|
||||
IsDraft = content.IsDraft,
|
||||
Description = GetDescription(content),
|
||||
WordCount = wordCount,
|
||||
ReadTime = CalculateReadTime(wordCount),
|
||||
PublishTime = content.Metadata.Date ?? DateTime.Now,
|
||||
HtmlContent = content.Content
|
||||
};
|
||||
|
||||
string originalHtml = Markdown.ToHtml(content.Content, markdownPipeline);
|
||||
|
||||
BlogEssay essay = new(
|
||||
content.Metadata.Title ?? content.BlogName, content.BlogName, content.IsDraft, publishDate, updateTime,
|
||||
description, wordCount, readTime, tags, originalHtml);
|
||||
logger.LogDebug("Render essay: {}", essay);
|
||||
if (content.Metadata.Tags is not null)
|
||||
{
|
||||
essay.Tags.AddRange(content.Metadata.Tags);
|
||||
}
|
||||
|
||||
essays.Add(essay);
|
||||
}
|
||||
|
||||
ConcurrentBag<BlogEssay> postProcessEssays = [];
|
||||
Parallel.ForEach(essays, essay =>
|
||||
{
|
||||
BlogEssay newEssay =
|
||||
essay.WithNewHtmlContent(Markdown.ToHtml(essay.HtmlContent, markdownPipeline));
|
||||
|
||||
postProcessEssays.Add(newEssay);
|
||||
logger.LogDebug("Render markdown file {}.", newEssay);
|
||||
});
|
||||
|
||||
IEnumerable<BlogEssay> postProcessedEssays = await PostProcess(essays);
|
||||
IEnumerable<BlogEssay> postProcessedEssays = await PostProcess(postProcessEssays);
|
||||
|
||||
foreach (BlogEssay essay in postProcessedEssays)
|
||||
{
|
||||
@@ -172,21 +182,28 @@ public sealed partial class RendererService(
|
||||
|
||||
string description = builder.ToString();
|
||||
|
||||
logger.LogDebug("Description of {name} is {desc}.", content.BlogName,
|
||||
logger.LogDebug("Description of {} is {}.", content.BlogName,
|
||||
description);
|
||||
return description;
|
||||
}
|
||||
|
||||
private (uint, string) GetWordCount(BlogContent content)
|
||||
private uint GetWordCount(BlogContent content)
|
||||
{
|
||||
uint count = MarkdownWordCounter.CountWord(content);
|
||||
int count = (from c in content.Content
|
||||
where char.IsLetterOrDigit(c)
|
||||
select c).Count();
|
||||
|
||||
logger.LogDebug("Word count of {blog} is {count}", content.BlogName,
|
||||
logger.LogDebug("Word count of {} is {}", content.BlogName,
|
||||
count);
|
||||
// 据说语文教学大纲规定,中国高中生阅读现代文的速度是600字每分钟
|
||||
uint second = count / 10;
|
||||
TimeSpan span = new(0, 0, (int)second);
|
||||
return (uint)count;
|
||||
}
|
||||
|
||||
return (count, span.ToString("mm'分'ss'秒'"));
|
||||
private static string CalculateReadTime(uint wordCount)
|
||||
{
|
||||
// 据说语文教学大纲规定,中国高中生阅读现代文的速度是600字每分钟
|
||||
int second = (int)wordCount / 10;
|
||||
TimeSpan span = new(0, 0, second);
|
||||
|
||||
return span.ToString("mm'分 'ss'秒'");
|
||||
}
|
||||
}
|
||||
39
YaeBlog/YaeBlog.csproj
Normal file
39
YaeBlog/YaeBlog.csproj
Normal file
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ImageFlow.NativeRuntime.ubuntu-x86_64" Version="2.1.0-rc11"/>
|
||||
<PackageReference Include="ImageFlow.Net" Version="0.13.2"/>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1"/>
|
||||
<PackageReference Include="AngleSharp" Version="1.1.0"/>
|
||||
<PackageReference Include="Markdig" Version="0.38.0"/>
|
||||
<PackageReference Include="YamlDotNet" Version="16.2.1"/>
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="EnsurePnpmInstalled" BeforeTargets="BeforeBuild">
|
||||
<Message Importance="low" Text="Ensure pnpm is installed..."/>
|
||||
<Exec Command="pnpm --version" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode"/>
|
||||
</Exec>
|
||||
|
||||
<Error Condition="$(ErrorCode) != 0" Text="Pnpm is not installed which is required for build."/>
|
||||
|
||||
<Message Importance="normal" Text="Installing pakages using pnpm..."/>
|
||||
<Exec Command="pnpm install"/>
|
||||
</Target>
|
||||
|
||||
<Target Name="TailwindGenerate" AfterTargets="EnsurePnpmInstalled" BeforeTargets="BeforeBuild" Condition="'$(_IsPublishing)' == 'yes'">
|
||||
<Message Importance="normal" Text="Generate css files using tailwind..."/>
|
||||
<Exec Command="pnpm tailwindcss -i wwwroot/tailwind.css -o $(IntermediateOutputPath)tailwind.g.css"/>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="$(IntermediateOutputPath)tailwind.g.css" Visible="false" TargetPath="wwwroot/tailwind.g.css"/>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -6,6 +6,5 @@
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using YaeBlog
|
||||
@using YaeBlog.Components
|
||||
@using BlazorSvgComponents
|
||||
@using BlazorSvgComponents.Models
|
||||
44
YaeBlog/appsettings.json
Normal file
44
YaeBlog/appsettings.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Tailwind": {
|
||||
"InputFile": "wwwroot/input.css",
|
||||
"OutputFile": "wwwroot/output.css"
|
||||
},
|
||||
"Blog": {
|
||||
"Root": "source",
|
||||
"Announcement": "博客锐意装修中,敬请期待!测试阶段如有问题还请海涵。",
|
||||
"StartYear": 2021,
|
||||
"Links": [
|
||||
{
|
||||
"Name": "Ichirinko",
|
||||
"Description": "这是个大哥",
|
||||
"Link": "https://ichirinko.top",
|
||||
"AvatarImage": "https://ichirinko-blog-img-1.oss-cn-shenzhen.aliyuncs.com/Pic_res/img/202209122110798.png"
|
||||
},
|
||||
{
|
||||
"Name": "志田千陽",
|
||||
"Description": "日出多值得",
|
||||
"Link": "https://zzachary.top/",
|
||||
"AvatarImage": "https://zzachary.top/img/ztqy_hub928259802d192ff5718c06370f0f2c4_48203_300x0_resize_q75_box.jpg"
|
||||
},
|
||||
{
|
||||
"Name": "不会写程序的晨旭",
|
||||
"Description": "一个普通大学生",
|
||||
"Link": "https://chenxutalk.top",
|
||||
"AvatarImage": "https://www.chenxutalk.top/img/photo.png"
|
||||
},
|
||||
{
|
||||
"Name": "万木长风",
|
||||
"Description": "世界渲染中...",
|
||||
"Link": "https://ryohai.fun",
|
||||
"AvatarImage": "https://ryohai.fun/icon.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
13
YaeBlog/docker-compose.yaml
Normal file
13
YaeBlog/docker-compose.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
blog:
|
||||
image: registry.cn-beijing.aliyuncs.com/jackfiled/blog:latest
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.blog.rule=Host(`rrricardo.top`) || Host(`www.rrricardo.top`)"
|
||||
- "traefik.http.services.blog.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.blog.tls=true"
|
||||
- "traefik.http.routers.blog.tls.certresolver=myresolver"
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
@@ -1,12 +1,11 @@
|
||||
---
|
||||
title: 2021年终总结
|
||||
date: 2022-01-12T16:27:19.0000000
|
||||
date: 2022-01-12 16:27:19
|
||||
tags:
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
---
|
||||
|
||||
|
||||
2021年已经过去,2022年已经来临。每每一年开始的时候,我都会展开一张纸或者新建一个文档,思量着又是一年时光,也该同诸大杂志一般,写几句意味深长的话语,怀念过去的时光,也祝福未来的自己。可往往脑海中已是三万字的长篇,落在笔头却又是一个字都没有了。
|
||||
如今跨年的时候已经过去,朋友圈中已经不见文案的踪影,我也该重新提笔,细说自己2021年中做过的种种。
|
||||
|
||||
@@ -24,7 +23,7 @@ tags:
|
||||
在前12年的学生生涯中,我们都在期待着这一次的暑假,以为在这个没有作业的假期里,我们就可以充分的享受人间的美好。可是,当时我们不知道,这人间的烦恼,可不止作业这一种,无论是突如其来的疫情导致开学延期,还是等待录取时的不安。
|
||||
虽说在暑假时,拥有了自己的笔记本电脑,可是在高中三年屯下的游戏还是没有玩几个,看来我也是“喜加一”的受害者。虽然在高考后入坑了原神,但是假期间我并没有太过投入的玩。
|
||||
暑假下定决心要好好的学一学,可是看着我gitee上暑假期间那稀疏的提交,我就知道我又摸了一个暑假的鱼。
|
||||

|
||||

|
||||
即使我想写的很多项目都没有被扎实的推进下来,但是学习的一些的C语言还是让我受益匪浅。
|
||||
现在看来,这个假期真是,**学也没有学好,耍也没有耍好**的典型。
|
||||
|
||||
BIN
YaeBlog/source/posts/2021-final/1.png
LFS
Normal file
BIN
YaeBlog/source/posts/2021-final/1.png
LFS
Normal file
Binary file not shown.
@@ -1,13 +1,12 @@
|
||||
---
|
||||
title: 2022年终总结
|
||||
date: 2022-12-30T14:58:12.0000000
|
||||
tags:
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
date: 2022-12-30 14:58:12
|
||||
---
|
||||
|
||||
|
||||
|
||||
2022是困难的一年。我们需要为2023年做好准备。
|
||||
|
||||
<!--more-->
|
||||
@@ -58,11 +57,11 @@ tags:
|
||||
|
||||
小小的总结一下:2022年可以算得上是一事无成的一年,还搞砸了不少的事情。在写代码上进展有限,成绩上大幅倒退,说好的六级英语和大学物理竞赛都没有参加,在年末应对疫情进展的时候更是把“不知所措”这个成语诠释的淋漓尽致。
|
||||
|
||||

|
||||

|
||||
|
||||
关于今年的人际交往和社会关系,我愿意用QQ2022年年终总结中的一张截屏来总结,这张图片透漏出一种无可救药的悲伤。
|
||||
|
||||

|
||||

|
||||
|
||||
## 展望
|
||||
|
||||
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg
LFS
Normal file
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-26-19-QQ_Image_1672381538441.jpg
LFS
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg
LFS
Normal file
BIN
YaeBlog/source/posts/2022-final/2022-12-30-14-28-12-QQ_Image_1672381543836.jpg
LFS
Normal file
Binary file not shown.
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: 2022年暑假碎碎念
|
||||
date: 2022-08-22T15:39:13.0000000
|
||||
tags:
|
||||
- 杂谈
|
||||
- 杂谈
|
||||
typora-root-url: 2022-summer-vacation
|
||||
date: 2022-08-22 15:39:13
|
||||
---
|
||||
|
||||
|
||||
在8个月的漫长寒假的最后两个月,~~也就是俗称的暑假中~~,我都干了些什么?
|
||||
|
||||
<!--more-->
|
||||
@@ -32,7 +32,7 @@ tags:
|
||||
- 下定决定要参加下一学期的物理竞赛,但是在听了讲座之后直接决定开学再开始学习,~~我知道我在家没法学习,俗称开摆~~
|
||||
- 又捡起了`Blender`,并在[Github](https://github.com/tanjian1998/bupt_minecraft)上找到了伟大的前辈们在`Minecraft`里复刻的老校区,希望能用`Blender`渲染几张图当作桌面。
|
||||
|
||||

|
||||

|
||||
|
||||
> 在此感谢所有为此付出过汗水的前辈们,让我这个即将搬入老校区的萌新能提前一睹老校区的风采。
|
||||
|
||||
BIN
YaeBlog/source/posts/2022-summer-vacation/result1.png
LFS
Normal file
BIN
YaeBlog/source/posts/2022-summer-vacation/result1.png
LFS
Normal file
Binary file not shown.
@@ -1,12 +1,11 @@
|
||||
---
|
||||
title: 2023年年终总结
|
||||
date: 2024-02-29T20:18:19.0000000
|
||||
tags:
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
- 杂谈
|
||||
- 年终总结
|
||||
date: 2024-2-29 20:18:19
|
||||
---
|
||||
|
||||
|
||||
虽然2023年已经过去了两个月,但是年终总结还是要发的。
|
||||
|
||||
<!--more-->
|
||||
@@ -45,7 +44,7 @@ tags:
|
||||
|
||||
2023年最令我吃惊的事情是我刷B站的时长:
|
||||
|
||||

|
||||

|
||||
|
||||
容易计算得出,我一共看了64天的B站,接近六分之一的时间都在看。虽然我确实有着在干活的时候黑听B站和把B站当作音乐播放器的习惯,但是这个时间未免有点太长了。下一年一定要在这个方面做出一定的改变,将更多的时间放在看书上面去,~~虽然写这句话的时候我就在黑听B站~~。
|
||||
|
||||
BIN
YaeBlog/source/posts/2023-final/image-20240303165826486.png
LFS
Normal file
BIN
YaeBlog/source/posts/2023-final/image-20240303165826486.png
LFS
Normal file
Binary file not shown.
@@ -6,7 +6,6 @@ tags:
|
||||
- 年终总结
|
||||
---
|
||||
|
||||
|
||||
欸,年终总结难道不是应该在新年当天发出吗,什么已经是新年第三天了?!
|
||||
|
||||
然而年末偶遇流感病毒,头疼脑热强如怪物,拼尽全力也无法战胜。
|
||||
@@ -71,7 +70,7 @@ tags:
|
||||
|
||||
不过我的B站观看时长再度增长30%,这好吗,这不好,~~有这么多时间刷B站,鬼知道你匆匆在哪了~~。
|
||||
|
||||

|
||||

|
||||
|
||||
### 未来
|
||||
|
||||
BIN
YaeBlog/source/posts/2024-final/image-20250115171809775.png
LFS
Normal file
BIN
YaeBlog/source/posts/2024-final/image-20250115171809775.png
LFS
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
@@ -1,11 +1,11 @@
|
||||
---
|
||||
title: 人生代码大作业初体验
|
||||
date: 2022-07-27T11:34:49.0000000
|
||||
tags:
|
||||
- 杂谈
|
||||
- 杂谈
|
||||
typora-root-url: big-homework
|
||||
date: 2022-07-27 11:34:49
|
||||
---
|
||||
|
||||
|
||||
在大学也呆了一年了,终于遇上了第一个需要多人合作的写代码项目。从四月底分组完成,任务部署下来到七月初接近尾声,在这两个多月的时间里,也算是经历了不少,学到了不少。
|
||||
|
||||
<!--more-->
|
||||
@@ -44,7 +44,7 @@ tags:
|
||||
|
||||
而且采用 `Git`还有一个好处,采用 `Github`的 `Insight`功能可以轻松的看出大家的贡献值()。
|
||||
|
||||

|
||||

|
||||
|
||||
## 一些技术上的收获
|
||||
|
||||
BIN
YaeBlog/source/posts/big-homework/1.png
LFS
Normal file
BIN
YaeBlog/source/posts/big-homework/1.png
LFS
Normal file
Binary file not shown.
@@ -1,12 +1,12 @@
|
||||
---
|
||||
title: 建立博客过程的记录
|
||||
date: 2022-04-08T11:52:32.0000000
|
||||
typora-root-url: 建立博客过程的记录
|
||||
date: 2022-04-08 11:52:32
|
||||
tags:
|
||||
- 技术笔记
|
||||
- 技术笔记
|
||||
---
|
||||
|
||||
|
||||
|
||||
当我已经在Python的浩瀚大海遨(zheng)游(zha)了半个暑假后,我决定尝试一下传说中程序员专用的学(zhuang)习(bi)手(fangfa)段(fa)——建立自己的个人博客。作为一个半懂不懂的Python程序员,心中冒出的第一个想法自然是采用Python的Django作为开发自己的个人博客的手段。然而,在阅读了[用Django搭建个人博客](https://www.dusaiphoto.com/article/2/)等的其他人搭建这类动态博客的过程记录之后,我便义无反顾的转向了采用javascript开发的博客框架[Hexo](https://hexo.io),<del>说好的Python信仰呢</del>。无他,唯简单尔。
|
||||
|
||||
<!--more-->
|
||||
@@ -131,7 +131,7 @@ Hexo init blog
|
||||
```
|
||||
Hexo会以blog为名称创建一个博客文件夹,这个文件夹的内容为
|
||||
|
||||

|
||||

|
||||
|
||||
`node_modules`文件夹是Hexo需要用到的一些npm依赖包的存放地址,`public`文件夹下是由Hexo渲染产生的静态博客文件,`scaffolds`文件夹是博客用到的模板文件,在默认情况下应该有`draft.md`,`page.md`,`post.md`三个模板文件。`themes`是Hexo中可以使用的主题文件。主题也是Hexo一个非常方便的设计,我们可以方便使用其他人编写的Hexo Themes,让自己的博客在不同的风格之间变换。`source`文件夹就是存放我们写作的博客的地方。一般这里面会有两个子文件夹,`_draft`, `_posts`。我们在里面在创建一个`img`文件夹,把自己的头像图片和网站的图标文件都放在里面,在之后的设置的时候使用。
|
||||
|
||||
@@ -146,7 +146,7 @@ INFO Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.
|
||||
|
||||
会在本地运行Hexo自带的一台静态博客服务器。我们用浏览器访问http://localhost:4000, 就可以看见Hexo博客的初始界面
|
||||
|
||||

|
||||

|
||||
|
||||
这便说明安装成功了,~~可以开香槟了~~
|
||||
|
||||
BIN
YaeBlog/source/posts/build-blog-record/1.png
LFS
Normal file
BIN
YaeBlog/source/posts/build-blog-record/1.png
LFS
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/build-blog-record/2.png
LFS
Normal file
BIN
YaeBlog/source/posts/build-blog-record/2.png
LFS
Normal file
Binary file not shown.
@@ -7,7 +7,6 @@ tags:
|
||||
---
|
||||
|
||||
|
||||
|
||||
我们编译是这样的,在本平台上编译只要敲三条命令就好了,而交叉编译要考虑的就很多了。
|
||||
|
||||
<!--more-->
|
||||
@@ -46,7 +45,7 @@ tags:
|
||||
|
||||
通常一份GNU工具链只能针对一个平台进行编译,但是LLVM工具链是一套先天的交叉编译工具链,例如对于`llc`工具,使用`llc --version`命令可以看见该编译器可以生成多种目标平台上的汇编代码:
|
||||
|
||||

|
||||

|
||||
|
||||
在使用`clang++`时加上`--target=<triple>`指定目标三元组就可以进行交叉编译。
|
||||
|
||||
@@ -63,7 +62,7 @@ int main()
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
看样子交叉编译也不是开箱即用的。最开始我们猜想系统提供的LLVM工具链没有被配置为交叉编译,因此尝试在本地自行编译一套LLVM工具链。
|
||||
|
||||
@@ -82,7 +81,7 @@ cmake ../llvm-project.src/llvm \
|
||||
|
||||
编译之后的成果会安装到`/usr/local/`目录下,而在`$PATH`环境变量中`/usr/local`位置将在`/usr`目录之前,因此调用时将会优先调用我们自行编译的LLVM工具链,而不是系统中安装的LLVM工具链。
|
||||
|
||||

|
||||

|
||||
|
||||
但是使用这套编译工具链仍然会爆出和之前一样的问题。说明这并不是系统安装LLVM工具链的问题。仔细一想也确实,这里提示找不到对应的头文件应该是找不到RISC-V架构之下的头文件——这里的也是交叉编译的主要问题所在:虽然LLVM工具链宣称自己是原生支持交叉编译的,但是没人宣称说标准库和头文件是原生的。这里我们就需要一个根文件系统来提供这些头文件和各种库文件。
|
||||
|
||||
@@ -199,7 +198,7 @@ clang++ --target=riscv64-linux-gnu --sysroot=$ROOTFS_DIR -fuse-ld=lld hello.cpp
|
||||
|
||||
第一个问题的回答是Arch Linux安装的LLVM工具是可以交叉编译的。虽然在Arch Linux官方构建LLVM工具链的[构建脚本](https://gitlab.archlinux.org/archlinux/packaging/packages/clang/-/blob/main/PKGBUILD?ref_type=heads)中没有使用`LLVM_TARGETS_TO_BUILD`参数,但是这个参数的默认值是`all`。这一点我们也可以通过实验来验证。
|
||||
|
||||
于是回到编译`llvm`的目录下执行`cat install_manifest.txt | sudo xargs rm`。
|
||||
于是回到编译`llvm`的目录下执行`cat install_manifest.txt | sudo xargs rm`。
|
||||
|
||||
第二个问题的回答可以使用实验来验证,首先安装`riscv64-linux-gnu-gcc`,然后将根文件系统的位置设置为`/usr/riscv64-linux-gnu`,重新编译上面的你好世界样例。编译之后可以正常执行。
|
||||
|
||||
@@ -230,4 +229,4 @@ export ROOTFS_DIR=<rootfs>
|
||||
|
||||
但是现在的.NET在RISC-V平台上还是废物一个,甚至连`dotnet new`都跑不过,下一步看看能不能运行一下运行时的测试集看看。
|
||||
|
||||

|
||||

|
||||
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824120646587.png
LFS
Normal file
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824120646587.png
LFS
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824121425007.png
LFS
Normal file
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824121425007.png
LFS
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824134158262.png
LFS
Normal file
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824134158262.png
LFS
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824153514149.png
LFS
Normal file
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824153514149.png
LFS
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824214145759.png
LFS
Normal file
BIN
YaeBlog/source/posts/build-dotnet-from-source/image-20240824214145759.png
LFS
Normal file
Binary file not shown.
@@ -1,12 +1,12 @@
|
||||
---
|
||||
title: C项目中有关头文件的一些问题
|
||||
date: 2022-05-08T11:35:19.0000000
|
||||
tags:
|
||||
- 技术笔记
|
||||
- C/C++
|
||||
- 技术笔记
|
||||
- C/C++
|
||||
typora-root-url: c-include-problems
|
||||
date: 2022-05-08 11:35:19
|
||||
---
|
||||
|
||||
|
||||
最近在完成一门`C`语言课程的大作业,课设老师要求我们将程序分模块的开发。在编写项目头文件的时候,遇到了一些令本菜鸡大开眼界的问题。
|
||||
|
||||
<!--more-->
|
||||
@@ -17,7 +17,7 @@ tags:
|
||||
|
||||
我项目的结构大致如图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
在`include`的头文件目录下有两个头文件,`rail.h`和`bus.h`,这两个头文件分别定义了两个结构体`rail_node_t`和`bus_t`。
|
||||
|
||||
@@ -68,7 +68,7 @@ typedef struct bus bus_t;
|
||||
|
||||
项目的`test`文件夹下是单元测试文件夹,但是在编译的时候会报错
|
||||
|
||||

|
||||

|
||||
|
||||
大意就是在一个google test内部的头文件中有几个函数找不到定义,这个函数都位于`io.h`这个头文件中。
|
||||
|
||||
BIN
YaeBlog/source/posts/c-include-problems/1.png
LFS
Normal file
BIN
YaeBlog/source/posts/c-include-problems/1.png
LFS
Normal file
Binary file not shown.
BIN
YaeBlog/source/posts/c-include-problems/2.png
LFS
Normal file
BIN
YaeBlog/source/posts/c-include-problems/2.png
LFS
Normal file
Binary file not shown.
@@ -5,14 +5,13 @@ tags:
|
||||
- 杂谈
|
||||
---
|
||||
|
||||
|
||||
2024年的中国计算机大会于10月24日到10月26日在浙江省金华市东阳市横店镇举办,而鄙人在下不才我,有幸受到实验室资助前去参观学习。
|
||||
|
||||
<!--more-->
|
||||
|
||||
首先开幕式镇楼。
|
||||
|
||||

|
||||

|
||||
|
||||
## 学术上
|
||||
|
||||
@@ -22,11 +21,11 @@ tags:
|
||||
|
||||
第一个报告是华为庞加莱实验室秦彬娟老师的《异构智算时代的操作系统演进》。报告高屋建瓴,从比较宏观的角度上介绍了当前异构融合操作系统诞生的背景、发展的方向。在报告中重点介绍了一种异构融合操作系统的设计思路:通过三层架构,基于互联池化技术,构建AI时代的融合算力系统。系统中的三层包括:(1)池化基础底层,包括多设备的融合和池化设备虚拟化;(2)异构融合核心子系统,例如异构融合调度系统、异构融合内存和异构融合存储系统;(3)异构核心服务。总的来说,这个报告在一定程度上勾勒出了未来一个异构融合操作系统应有的各项功能,但是显然这一操作系统的实现还存在着明显的困难。
|
||||
|
||||

|
||||

|
||||
|
||||
下面一个报告是较为有干货的报告,北京航空航天大学刘瀚骋老师的《异构融合OS及多样性内存管理框架》。报告中介绍了一个称作`FMMU`的系统,是对于异构融合操作系统中内存管理系统的探索。报告中首先介绍了内存池化技术对于异构融合操作系统的重要性,指出分布式共享内存(Distributed Shared Memory)可能是实现内存池化技术的未来。然后介绍了将部分内存管理中的计算卸载到可编程网络硬件中来加速分布式内存访问的新思路。最后在报告中提到了内存管理技术如何解决错误预测和错误回复的问题。虽然在听的时候没太注意,但是现在总结的时候才发现这个报告的思路似乎有点混乱,尤其是最后一点和内存管理系统并没有什么直接的关系,而且这个内存管理系统似乎不是**异构系统**的内存管理,反而是分布式系统的内存管理。不过总的来说,这个报告还是非常实际的,介绍了不少当前异构融合操作系统中的内存管理面临的问题和解决问题的探索。
|
||||
|
||||

|
||||

|
||||
|
||||
第三个报告是国防科技大学李东升老师的《异构计算环境下的分布式深度学习训练》。报告首先从李老师的主业——并行计算起手,介绍了深度学习训练过程中主要的各种并行方法,例如数据并行、模型并行和混合并行等,指出目前大模型的并行训练存在着计算/存储/通信难的问题。因此,提出了一个智能模型训练并行任务划分方法:(1)基于符号算子的计算图定义方法;(2)面向Transformer模型的流水线并行任务划分方法;(3)异构资源感知的流水线并行任务划分方法。然后针对分布式模型训练中通信调度存在的通信墙、数据依赖关系复杂等的问题,提出综合词嵌入表的稀疏通信调度技术、流水线并行的P2P通信调度技术、模型计算的统一操作执行引擎和网络链路感知的通信执行引擎的通信调度技术。最后提到了智能模型训练 的内存优化技术,针对现有重计算技术(re-computing)和存储交换(swapping)技术存在的问题,提出了一种面向大型智能模型训练的细粒度内存优化方法`DELTA`。
|
||||
|
||||
@@ -50,7 +49,7 @@ Plane讨论没有参加。
|
||||
|
||||
第二个报告是南京大学冯新宇老师的《基于仓颉语言的嵌入式DSL开发》,同时冯新宇老师也是仓颉语言的首席架构师。冯老师的这个报告主要聚焦于仓颉语言提供的嵌入式DSL能力,而嵌入式DSL这一设计范式已经在前端开发中展现了不俗的潜力。报告中介绍了嵌入式DSL出现的背景,仓颉中为了提供嵌入式DSL而引入的语法糖、仓颉提供的嵌入式DSL工具箱等。虽然仓颉语言是一个主要面向上层应用开发的语言,但是仓颉中丰富的DSL能力还是给异构编程模型的设计提供了不少的启发。而且目前在各种深度学习编译器中DSL的应用也非常广泛,例如`triton`。
|
||||
|
||||

|
||||

|
||||
|
||||
第三个报告是在存算一体的芯片上做数据库的加速,第四个报告是OpenHarmony上`ArkTS`程序的静态分析,都没怎么听。
|
||||
|
||||
BIN
YaeBlog/source/posts/cncc-2024/image-20241102211959206.png
LFS
Normal file
BIN
YaeBlog/source/posts/cncc-2024/image-20241102211959206.png
LFS
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user