-
@Model.Category.Name
+
@(Model.Category?.Name ?? "未分类")
@Model.Title
@Model.LastUpdateTime.ToShortDateString()
@Model.Summary.Limit(50)
diff --git a/src/StarBlog.Web/gulpfile.js b/src/StarBlog.Web/gulpfile.js
index c18e8a7..a374e6c 100644
--- a/src/StarBlog.Web/gulpfile.js
+++ b/src/StarBlog.Web/gulpfile.js
@@ -1,4 +1,4 @@
-///
+///
"use strict";
//加载使用到的 gulp 插件
@@ -61,7 +61,7 @@ const customLibs = [
{name: 'element-ui/lib/theme-chalk', dist: './node_modules/element-ui/lib/theme-chalk/index.css'},
{name: 'element-ui/lib/theme-chalk/fonts', dist: './node_modules/element-ui/lib/theme-chalk/fonts/*.*'},
{name: 'font-awesome', dist: './node_modules/@fortawesome/fontawesome-free/**/*.*'},
- {name: 'highlight.js', dist: './node_modules/highlight.js/**/*.*'},
+ {name: 'highlight', dist: './node_modules/highlight.js/**/*.*'},
{name: 'github-markdown-css', dist: './node_modules/github-markdown-css/*.css'},
{name: 'imagesloaded', dist: './node_modules/imagesloaded/*.js'},
]
@@ -134,4 +134,4 @@ gulp.task("concat", gulp.series(["concat:js", "concat:css"]))
gulp.task("auto", () => {
gulp.watch(paths.css, gulp.series(["min:css", "concat:css"]));
gulp.watch(paths.js, gulp.series(["min:js", "concat:js"]));
-});
\ No newline at end of file
+});
diff --git a/src/StarBlog.Web/package.json b/src/StarBlog.Web/package.json
index 80d7b50..4061d51 100644
--- a/src/StarBlog.Web/package.json
+++ b/src/StarBlog.Web/package.json
@@ -9,6 +9,7 @@
"author": "DealiAxy",
"license": "ISC",
"devDependencies": {
+ "bower": "^1.8.14",
"gulp": "^4.0.2",
"gulp-changed": "^4.0.3",
"gulp-clean-css": "^4.3.0",
@@ -29,9 +30,12 @@
"editor.md-ext": "^1.5.4",
"element-ui": "^2.15.13",
"github-markdown-css": "^5.1.0",
+ "highlight.js": "^11.11.1",
"imagesloaded": "^5.0.0",
"jquery": "^3.6.0",
"masonry-layout": "^4.2.2",
+ "popper.js": "^1.16.1",
+ "prismjs": "^1.30.0",
"vue": "^2.6.14"
}
}
diff --git a/tools/BlogImageOptimizer/BlogImageOptimizer.csproj b/tools/BlogImageOptimizer/BlogImageOptimizer.csproj
index f2d4f4f..e94fb07 100644
--- a/tools/BlogImageOptimizer/BlogImageOptimizer.csproj
+++ b/tools/BlogImageOptimizer/BlogImageOptimizer.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
enable
enable
Linux
diff --git a/tools/DataProc/DataProc.csproj b/tools/DataProc/DataProc.csproj
index f20b7d6..8fb9cab 100644
--- a/tools/DataProc/DataProc.csproj
+++ b/tools/DataProc/DataProc.csproj
@@ -54,6 +54,7 @@
+
diff --git a/tools/DataProc/src/Data/Models/AuditLog.cs b/tools/DataProc/src/Data/Models/AuditLog.cs
index 73d066d..478810d 100644
--- a/tools/DataProc/src/Data/Models/AuditLog.cs
+++ b/tools/DataProc/src/Data/Models/AuditLog.cs
@@ -11,7 +11,7 @@ public class AuditLog {
///
/// 事件类型(例如:登录、登出、数据修改等)
///
- public string EventType { get; set; }
+ public string? EventType { get; set; }
///
/// 执行操作的用户名
diff --git a/tools/DataProc/src/Framework/Extensions/EFCoreItegration.cs b/tools/DataProc/src/Framework/Extensions/EFCoreItegration.cs
index 7fbae45..6d400ca 100644
--- a/tools/DataProc/src/Framework/Extensions/EFCoreItegration.cs
+++ b/tools/DataProc/src/Framework/Extensions/EFCoreItegration.cs
@@ -1,5 +1,4 @@
-using DataProc.Data;
-using Microsoft.Extensions.DependencyInjection;
+using DataProc.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -8,8 +7,10 @@ namespace DataProc.Framework.Extensions;
public static class EFCoreItegration {
public static void AddDefaultEFCoreItegration(this FluentConsoleApp app) {
+ var connectionString = app.Configuration.GetConnectionString("SQLite")
+ ?? throw new InvalidOperationException("缺少 SQLite 连接字符串配置。");
app.Services.AddDbContext(options => {
- options.UseSqlite(app.Configuration.GetConnectionString("SQLite"));
+ options.UseSqlite(connectionString);
});
}
-}
\ No newline at end of file
+}
diff --git a/tools/DataProc/src/Services/DataInsightScript.cs b/tools/DataProc/src/Services/DataInsightScript.cs
index 07b050d..73b0cae 100644
--- a/tools/DataProc/src/Services/DataInsightScript.cs
+++ b/tools/DataProc/src/Services/DataInsightScript.cs
@@ -1,4 +1,4 @@
-using FluentResults;
+using FluentResults;
using FreeSql;
using Microsoft.Extensions.Logging;
using StarBlog.Data.Models;
@@ -82,10 +82,11 @@ public Dictionary GetSections(params string[] titles) {
}
}
- private static string ExtractSectionRegex(string content, string title) {
+ private static string ExtractSectionRegex(string? content, string title) {
+ if (string.IsNullOrWhiteSpace(content)) return string.Empty;
// 匹配目标标题开始,直到下一个标题或文档末尾
string pattern = $@"##\s*{title}\s*([\s\S]*?)(?=\n##|$)";
var match = Regex.Match(content, pattern);
return match.Success ? match.Groups[1].Value.Trim() : string.Empty;
}
-}
\ No newline at end of file
+}
diff --git a/tools/DataProc/src/Services/ImageOptimizer.cs b/tools/DataProc/src/Services/ImageOptimizer.cs
index 72979f8..fb54581 100644
--- a/tools/DataProc/src/Services/ImageOptimizer.cs
+++ b/tools/DataProc/src/Services/ImageOptimizer.cs
@@ -1,4 +1,4 @@
-using FluentResults;
+using FluentResults;
using FreeSql;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
@@ -48,9 +48,9 @@ public class ProcessingStats {
}
public class ImageOptimizer : IService {
- private readonly ILogger logger;
- private readonly IConfiguration conf;
- private readonly IBaseRepository postRepo;
+ private readonly ILogger _logger;
+ private readonly IConfiguration _conf;
+ private readonly IBaseRepository _postRepo;
// 统计信息 - 使用线程安全的集合
private readonly ConcurrentBag _processingStats = new();
@@ -74,9 +74,9 @@ public ImageOptimizer(
ILogger logger,
IConfiguration conf,
IBaseRepository postRepo) {
- this.logger = logger;
- this.conf = conf;
- this.postRepo = postRepo;
+ _logger = logger;
+ _conf = conf;
+ _postRepo = postRepo;
// 设置最大并发数,默认为CPU核心数
_maxConcurrency = conf.GetValue("ImageOptimizer:MaxConcurrency", Environment.ProcessorCount);
@@ -86,9 +86,9 @@ public ImageOptimizer(
_compressionOptions = InitializeCompressionOptions(conf);
_compressionModes = _compressionOptions.Modes;
- logger.LogInformation("图片压缩器初始化 - 最大并发数: {MaxConcurrency}", _maxConcurrency);
- logger.LogInformation("默认压缩模式: {DefaultMode}", _compressionOptions.DefaultMode);
- logger.LogInformation("可用压缩模式: {ModeCount} 种", _compressionModes.Count);
+ _logger.LogInformation("图片压缩器初始化 - 最大并发数: {MaxConcurrency}", _maxConcurrency);
+ _logger.LogInformation("默认压缩模式: {DefaultMode}", _compressionOptions.DefaultMode);
+ _logger.LogInformation("可用压缩模式: {ModeCount} 种", _compressionModes.Count);
// 显示压缩模式详情
LogCompressionModes();
@@ -177,7 +177,7 @@ private void ApplyImageResize(Image image, CompressionMode mode) {
var newWidth = (int)(image.Width * scale);
var newHeight = (int)(image.Height * scale);
- logger.LogDebug("调整图片尺寸: {OriginalWidth}×{OriginalHeight} -> {NewWidth}×{NewHeight} (模式: {ModeName})",
+ _logger.LogDebug("调整图片尺寸: {OriginalWidth}×{OriginalHeight} -> {NewWidth}×{NewHeight} (模式: {ModeName})",
image.Width, image.Height, newWidth, newHeight, mode.Name);
image.Mutate(x => x.Resize(newWidth, newHeight));
@@ -187,44 +187,44 @@ private void ApplyImageResize(Image image, CompressionMode mode) {
/// 记录压缩模式配置信息
///
private void LogCompressionModes() {
- logger.LogInformation("");
- logger.LogInformation("🎨 压缩模式配置:");
+ _logger.LogInformation("");
+ _logger.LogInformation("🎨 压缩模式配置:");
foreach (var (key, mode) in _compressionModes) {
var isDefault = key == _compressionOptions.DefaultMode ? " [默认]" : "";
var sizeInfo = mode.MaxWidth == int.MaxValue ? "保持原尺寸" : $"{mode.MaxWidth}×{mode.MaxHeight}";
- logger.LogInformation(" • {ModeName}{IsDefault}: {SizeInfo}, 质量{Quality}%",
+ _logger.LogInformation(" • {ModeName}{IsDefault}: {SizeInfo}, 质量{Quality}%",
mode.Name, isDefault, sizeInfo, mode.Quality);
- logger.LogInformation(" {Description}", mode.Description);
+ _logger.LogInformation(" {Description}", mode.Description);
}
- logger.LogInformation("");
+ _logger.LogInformation("");
}
public async Task Run() {
- var posts = await postRepo.Select.ToListAsync();
- var wwwroot = conf.GetValue("StarBlog:wwwroot");
+ var posts = await _postRepo.Select.ToListAsync();
+ var wwwroot = _conf.GetValue("StarBlog:wwwroot");
if (string.IsNullOrWhiteSpace(wwwroot)) {
throw new Exception("wwwroot 配置错误");
}
// 获取输出目录配置
- var outputBaseDir = conf.GetValue("ImageOptimizer:OutputDir");
+ var outputBaseDir = _conf.GetValue("ImageOptimizer:OutputDir");
if (string.IsNullOrWhiteSpace(outputBaseDir)) {
throw new Exception("未配置输出目录");
}
- logger.LogInformation("压缩后图片将保存到: {OutputBaseDir}", outputBaseDir);
+ _logger.LogInformation("压缩后图片将保存到: {OutputBaseDir}", outputBaseDir);
// 确保输出基础目录存在
Directory.CreateDirectory(outputBaseDir);
- logger.LogInformation("开始处理 {TotalPosts} 篇文章的图片", posts.Count);
+ _logger.LogInformation("开始处理 {TotalPosts} 篇文章的图片", posts.Count);
var processedCount = 0;
foreach (var post in posts) {
processedCount++;
- logger.LogInformation("处理进度: {Current}/{Total} - 文章 {PostId}",
+ _logger.LogInformation("处理进度: {Current}/{Total} - 文章 {PostId}",
processedCount, posts.Count, post.Id);
var blogImageDir = Path.Combine(wwwroot, "media", "blog", post.Id);
@@ -236,7 +236,7 @@ public async Task Run() {
var outputDir = Path.Combine(outputBaseDir, post.Id);
Directory.CreateDirectory(outputDir);
- logger.LogInformation("处理文章 {PostId} 的图片, 源目录: {SourceDir}, 输出目录: {OutputDir}",
+ _logger.LogInformation("处理文章 {PostId} 的图片, 源目录: {SourceDir}, 输出目录: {OutputDir}",
post.Id, blogImageDir, outputDir);
var files = Directory.GetFiles(blogImageDir);
@@ -253,7 +253,7 @@ public async Task Run() {
var compressionTasks = imageFiles.Select(async file => {
await _concurrencyLimiter.WaitAsync();
try {
- logger.LogInformation("处理图片 {FileName}", file);
+ _logger.LogInformation("处理图片 {FileName}", file);
Interlocked.Increment(ref _processedImages);
var result = await CompressImage(file, outputDir);
@@ -279,7 +279,7 @@ public async Task Run() {
var outputFormat = Path.GetExtension(result.NewFilePath).ToLower();
_formatStats.AddOrUpdate(outputFormat, 1, (key, value) => value + 1);
- logger.LogInformation("图片压缩成功: {OriginalFile} -> {NewFile}, 压缩率: {CompressionRatio:P2}",
+ _logger.LogInformation("图片压缩成功: {OriginalFile} -> {NewFile}, 压缩率: {CompressionRatio:P2}",
originalFileName, newFileName, result.CompressionRatio);
} else {
Interlocked.Increment(ref _failedCompressions);
@@ -288,7 +288,7 @@ public async Task Run() {
return result;
}
catch (Exception ex) {
- logger.LogError(ex, "压缩图片失败: {FileName}", file);
+ _logger.LogError(ex, "压缩图片失败: {FileName}", file);
Interlocked.Increment(ref _failedCompressions);
return null;
}
@@ -301,7 +301,7 @@ public async Task Run() {
var results = await Task.WhenAll(compressionTasks);
// 收集文章级别的统计信息
- foreach (var result in results.Where(r => r != null)) {
+ foreach (var result in results.OfType()) {
postStats.OriginalSize += result.OriginalSize;
postStats.CompressedSize += result.CompressedSize;
postStats.ProcessedImages++;
@@ -324,12 +324,12 @@ public async Task Run() {
var updatedContent = UpdateMarkdownImageLinks(post, fileNameMappings);
if (updatedContent != post.Content) {
post.Content = updatedContent;
- await postRepo.UpdateAsync(post);
- logger.LogInformation("已更新文章 {PostId} 的图片链接", post.Id);
+ await _postRepo.UpdateAsync(post);
+ _logger.LogInformation("已更新文章 {PostId} 的图片链接", post.Id);
}
}
catch (Exception ex) {
- logger.LogError(ex, "更新文章 {PostId} 的图片链接失败", post.Id);
+ _logger.LogError(ex, "更新文章 {PostId} 的图片链接失败", post.Id);
// 数据库更新失败不影响继续处理其他文章
}
}
@@ -345,37 +345,37 @@ public async Task Run() {
/// 生成汇总报告
///
private void GenerateSummaryReport(string outputBaseDir, string wwwroot) {
- logger.LogInformation("");
- logger.LogInformation("🎉 ================ 图片压缩汇总报告 ================");
- logger.LogInformation("");
+ _logger.LogInformation("");
+ _logger.LogInformation("🎉 ================ 图片压缩汇总报告 ================");
+ _logger.LogInformation("");
// 基本统计
- logger.LogInformation("📊 基本统计:");
- logger.LogInformation(" • 处理的文章数量: {ProcessedPosts}", _processingStats.Count);
- logger.LogInformation(" • 发现的图片总数: {TotalImages}", _totalImages);
- logger.LogInformation(" • 处理的图片数量: {ProcessedImages}", _processedImages);
- logger.LogInformation(" • 成功压缩数量: {SuccessfulCompressions}", _successfulCompressions);
- logger.LogInformation(" • 压缩失败数量: {FailedCompressions}", _failedCompressions);
- logger.LogInformation("");
+ _logger.LogInformation("📊 基本统计:");
+ _logger.LogInformation(" • 处理的文章数量: {ProcessedPosts}", _processingStats.Count);
+ _logger.LogInformation(" • 发现的图片总数: {TotalImages}", _totalImages);
+ _logger.LogInformation(" • 处理的图片数量: {ProcessedImages}", _processedImages);
+ _logger.LogInformation(" • 成功压缩数量: {SuccessfulCompressions}", _successfulCompressions);
+ _logger.LogInformation(" • 压缩失败数量: {FailedCompressions}", _failedCompressions);
+ _logger.LogInformation("");
// 文件大小统计
var totalSavedBytes = _totalOriginalSize - _totalCompressedSize;
var overallCompressionRatio = _totalOriginalSize > 0 ? 1.0 - (double)_totalCompressedSize / _totalOriginalSize : 0;
- logger.LogInformation("💾 文件大小统计:");
- logger.LogInformation(" • 原始总大小: {OriginalSize}", FormatFileSize(_totalOriginalSize));
- logger.LogInformation(" • 压缩后总大小: {CompressedSize}", FormatFileSize(_totalCompressedSize));
- logger.LogInformation(" • 节省空间: {SavedSize}", FormatFileSize(totalSavedBytes));
- logger.LogInformation(" • 总体压缩率: {CompressionRatio:P2}", overallCompressionRatio);
- logger.LogInformation("");
+ _logger.LogInformation("💾 文件大小统计:");
+ _logger.LogInformation(" • 原始总大小: {OriginalSize}", FormatFileSize(_totalOriginalSize));
+ _logger.LogInformation(" • 压缩后总大小: {CompressedSize}", FormatFileSize(_totalCompressedSize));
+ _logger.LogInformation(" • 节省空间: {SavedSize}", FormatFileSize(totalSavedBytes));
+ _logger.LogInformation(" • 总体压缩率: {CompressionRatio:P2}", overallCompressionRatio);
+ _logger.LogInformation("");
// 格式统计
if (_formatStats.Count > 0) {
- logger.LogInformation("📁 输出格式统计:");
+ _logger.LogInformation("📁 输出格式统计:");
foreach (var format in _formatStats.OrderByDescending(x => x.Value)) {
- logger.LogInformation(" • {Format}: {Count} 个文件", format.Key.ToUpper(), format.Value);
+ _logger.LogInformation(" • {Format}: {Count} 个文件", format.Key.ToUpper(), format.Value);
}
- logger.LogInformation("");
+ _logger.LogInformation("");
}
// 前10个压缩效果最好的文章
@@ -386,44 +386,44 @@ private void GenerateSummaryReport(string outputBaseDir, string wwwroot) {
.ToList();
if (topCompressionPosts.Count > 0) {
- logger.LogInformation("🏆 压缩效果最佳的文章 (前10名):");
+ _logger.LogInformation("🏆 压缩效果最佳的文章 (前10名):");
for (int i = 0; i < topCompressionPosts.Count; i++) {
var post = topCompressionPosts[i];
- logger.LogInformation(" {Rank}. 文章 {PostId}: 节省 {SavedSize}, 压缩率 {CompressionRatio:P2} ({SuccessfulCount}/{TotalCount} 张图片)",
+ _logger.LogInformation(" {Rank}. 文章 {PostId}: 节省 {SavedSize}, 压缩率 {CompressionRatio:P2} ({SuccessfulCount}/{TotalCount} 张图片)",
i + 1, post.PostId, FormatFileSize(post.SavedBytes), post.CompressionRatio,
post.SuccessfulCompressions, post.TotalImages);
}
- logger.LogInformation("");
+ _logger.LogInformation("");
}
// 失败统计
var failedPosts = _processingStats.Where(p => p.FailedCompressions > 0).ToList();
if (failedPosts.Count > 0) {
- logger.LogInformation("⚠️ 压缩失败统计:");
+ _logger.LogInformation("⚠️ 压缩失败统计:");
foreach (var post in failedPosts.OrderByDescending(p => p.FailedCompressions)) {
- logger.LogInformation(" • 文章 {PostId}: {FailedCount} 张图片压缩失败",
+ _logger.LogInformation(" • 文章 {PostId}: {FailedCount} 张图片压缩失败",
post.PostId, post.FailedCompressions);
}
- logger.LogInformation("");
+ _logger.LogInformation("");
}
// 目录信息
- logger.LogInformation("📂 目录信息:");
- logger.LogInformation(" • 原始图片目录: {OriginalDir}", Path.Combine(wwwroot, "media", "blog"));
- logger.LogInformation(" • 压缩后图片目录: {OutputDir}", outputBaseDir);
- logger.LogInformation("");
+ _logger.LogInformation("📂 目录信息:");
+ _logger.LogInformation(" • 原始图片目录: {OriginalDir}", Path.Combine(wwwroot, "media", "blog"));
+ _logger.LogInformation(" • 压缩后图片目录: {OutputDir}", outputBaseDir);
+ _logger.LogInformation("");
// 操作建议
- logger.LogInformation("💡 下一步操作建议:");
- logger.LogInformation(" 1. 检查压缩后的图片质量和效果");
- logger.LogInformation(" 2. 确认无误后,可以手动替换原目录中的图片文件");
- logger.LogInformation(" 3. 建议先备份原始图片目录");
+ _logger.LogInformation("💡 下一步操作建议:");
+ _logger.LogInformation(" 1. 检查压缩后的图片质量和效果");
+ _logger.LogInformation(" 2. 确认无误后,可以手动替换原目录中的图片文件");
+ _logger.LogInformation(" 3. 建议先备份原始图片目录");
if (failedPosts.Count > 0) {
- logger.LogInformation(" 4. 检查压缩失败的图片,可能需要手动处理");
+ _logger.LogInformation(" 4. 检查压缩失败的图片,可能需要手动处理");
}
- logger.LogInformation("");
- logger.LogInformation("🎉 ================ 报告结束 ================");
- logger.LogInformation("");
+ _logger.LogInformation("");
+ _logger.LogInformation("🎉 ================ 报告结束 ================");
+ _logger.LogInformation("");
}
///
@@ -496,7 +496,7 @@ private async Task CompressImage(string imagePath, string out
};
}
- logger.LogDebug("选择的输出格式: {OutputFormat}", outputFormat);
+ _logger.LogDebug("选择的输出格式: {OutputFormat}", outputFormat);
return new CompressionResult {
Success = true,
@@ -531,7 +531,7 @@ private async Task CompressImage(string imagePath, string out
bool hasTransparency = HasTransparency(image);
bool isSimpleGraphic = IsSimpleGraphic(image);
- logger.LogDebug("图片分析 - 透明度: {HasTransparency}, 图片类型: {ImageType}",
+ _logger.LogDebug("图片分析 - 透明度: {HasTransparency}, 图片类型: {ImageType}",
hasTransparency, isSimpleGraphic ? "图形/图标" : "照片/复杂图像");
// 智能选择格式
@@ -588,7 +588,7 @@ private async Task CompressImage(string imagePath, string out
var webpSize = new FileInfo(tempWebp).Length;
var jpegSize = new FileInfo(tempJpeg).Length;
- logger.LogDebug("格式比较 - WebP: {WebpSize} bytes, JPEG: {JpegSize} bytes", webpSize, jpegSize);
+ _logger.LogDebug("格式比较 - WebP: {WebpSize} bytes, JPEG: {JpegSize} bytes", webpSize, jpegSize);
// 选择更小的格式
if (webpSize <= jpegSize) {
@@ -614,8 +614,9 @@ private async Task CompressImage(string imagePath, string out
///
private static bool HasTransparency(Image image) {
// 检查像素格式是否支持透明度
+ var pixelTypeText = image.PixelType.ToString() ?? string.Empty;
return image.PixelType.BitsPerPixel == 32 ||
- image.PixelType.ToString().Contains("Rgba");
+ pixelTypeText.Contains("Rgba", StringComparison.OrdinalIgnoreCase);
}
///
@@ -662,7 +663,7 @@ private string UpdateMarkdownImageLinks(Post post, Dictionary fi
linkInline.Url = Path.Combine(directory, newFileName).Replace('\\', '/');
}
- logger.LogDebug("更新图片链接: {OldUrl} -> {NewUrl}", imgUrl, linkInline.Url);
+ _logger.LogDebug("更新图片链接: {OldUrl} -> {NewUrl}", imgUrl, linkInline.Url);
}
}
}
@@ -684,4 +685,4 @@ public class CompressionResult {
public long OriginalSize { get; set; }
public long CompressedSize { get; set; }
public double CompressionRatio { get; set; }
-}
\ No newline at end of file
+}
diff --git a/tools/DataProc/src/Services/LLM.cs b/tools/DataProc/src/Services/LLM.cs
index e3e432d..35246fd 100644
--- a/tools/DataProc/src/Services/LLM.cs
+++ b/tools/DataProc/src/Services/LLM.cs
@@ -1,4 +1,4 @@
-using System.ClientModel;
+using System.ClientModel;
using DataProc.Entities;
using FluentResults;
using Microsoft.Extensions.AI;
@@ -10,12 +10,14 @@ namespace DataProc.Services;
public class LLM : IService {
public LLM(IOptions llmOptions) {
var llmConfig = llmOptions.Value;
+ var endpoint = llmConfig.Endpoint
+ ?? throw new InvalidOperationException("LLM Endpoint 未配置。");
ChatClient = new OpenAIClient(
- new ApiKeyCredential(llmConfig.Key),
+ new ApiKeyCredential(llmConfig.Key ?? throw new InvalidOperationException("LLM Key 未配置。")),
new OpenAIClientOptions {
- Endpoint = new Uri(llmConfig.Endpoint)
+ Endpoint = new Uri(endpoint)
}
- ).GetChatClient(llmConfig.Model).AsIChatClient();
+ ).GetChatClient(llmConfig.Model ?? throw new InvalidOperationException("LLM Model 未配置。")).AsIChatClient();
}
public IChatClient ChatClient { get; }
@@ -81,4 +83,4 @@ public IAsyncEnumerable GenerateChatReplyStreamAsync(params
throw new Exception($"AI聊天回复流生成失败: {ex.Message}", ex);
}
}
-}
\ No newline at end of file
+}
diff --git a/tools/StarBlogBackup/StarBlog.BackupTool.csproj b/tools/StarBlogBackup/StarBlog.BackupTool.csproj
index 8bbc4f2..2bff5f4 100644
--- a/tools/StarBlogBackup/StarBlog.BackupTool.csproj
+++ b/tools/StarBlogBackup/StarBlog.BackupTool.csproj
@@ -10,6 +10,7 @@
+