证书配置

This commit is contained in:
马梓彭 2026-04-22 07:47:07 +08:00
parent 2180950c5e
commit 66a93af122

View File

@ -88,7 +88,13 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
// 设置 QuestPDF 许可证
QuestPDF.Settings.License = LicenseType.Community;
// 尝试配置中文字体
// 配置 QuestPDF 字体管理
// QuestPDF 2025.12+ 会自动使用 SkiaSharp 的字体检测
// 我们不需要显式注册字体,只需要确保系统中有可用的中文字体
_logger.LogInformation("QuestPDF 配置完成,使用 SkiaSharp 字体系统");
// 尝试配置中文字体(检测并记录)
ConfigureChineseFonts();
_logger.LogInformation("PDF 字体系统初始化完成");
@ -102,7 +108,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
}
/// <summary>
/// 配置中文字体 - 增强版,支持字体文件检测和注册
/// 配置中文字体 - 修复版,正确使用 QuestPDF 字体注册机制
/// </summary>
private void ConfigureChineseFonts()
{
@ -116,105 +122,83 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
_logger.LogInformation($"当前操作系统: {(isLinux ? "Linux" : isWindows ? "Windows" : "其他")}");
// Linux 字体路径列表
// Linux 字体路径列表(优先级排序)
var linuxFontPaths = new[]
{
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/local/share/fonts/wqy-zenhei.ttc"
("/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", "WenQuanYi Zen Hei"),
("/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", "WenQuanYi Micro Hei"),
("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", "Noto Sans CJK SC"),
("/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", "Noto Sans CJK SC"),
("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "DejaVu Sans"),
("/usr/local/share/fonts/wqy-zenhei.ttc", "WenQuanYi Zen Hei")
};
// Windows 字体路径列表
// Windows 字体路径列表(优先级排序)
var windowsFontPaths = new[]
{
@"C:\Windows\Fonts\msyh.ttc", // 微软雅黑
@"C:\Windows\Fonts\simsun.ttc", // 宋体
@"C:\Windows\Fonts\simhei.ttf" // 黑体
(@"C:\Windows\Fonts\msyh.ttc", "Microsoft YaHei"), // 微软雅黑
(@"C:\Windows\Fonts\simsun.ttc", "SimSun"), // 宋体
(@"C:\Windows\Fonts\simhei.ttf", "SimHei"), // 黑体
(@"C:\Windows\Fonts\msyhl.ttc", "Microsoft YaHei Light"),
(@"C:\Windows\Fonts\simkai.ttf", "KaiTi")
};
string fontPathToRegister = null;
string fontNameToUse = null;
// 尝试查找字体文件
if (isLinux)
// 尝试查找并注册字体文件
var fontPathsToCheck = isLinux ? linuxFontPaths : windowsFontPaths;
foreach (var (path, name) in fontPathsToCheck)
{
_logger.LogInformation("正在 Linux 系统中查找字体文件...");
foreach (var path in linuxFontPaths)
if (File.Exists(path))
{
if (File.Exists(path))
_logger.LogInformation($"找到字体文件: {path} ({name})");
fontPathToRegister = path;
fontNameToUse = name;
try
{
_logger.LogInformation($"找到字体文件: {path}");
fontPathToRegister = path;
// 尝试在 QuestPDF 中注册字体(如果支持)
// QuestPDF 会自动搜索系统字体,也可以显式注册
_logger.LogInformation($"尝试注册字体: {fontNameToUse}");
if (path.Contains("wqy-zenhei")) fontNameToUse = "WenQuanYi Zen Hei";
else if (path.Contains("wqy-microhei")) fontNameToUse = "WenQuanYi Micro Hei";
else if (path.Contains("Noto")) fontNameToUse = "Noto Sans CJK SC";
else if (path.Contains("DejaVu")) fontNameToUse = "DejaVu Sans";
else fontNameToUse = "CustomChineseFont";
break;
// 先测试字体是否可用
if (TestFontAvailability(fontNameToUse))
{
_selectedChineseFont = fontNameToUse;
_logger.LogInformation($"字体配置成功: {_selectedChineseFont}");
break;
}
}
else
catch (Exception ex)
{
_logger.LogDebug($"字体文件不存在: {path}");
_logger.LogWarning(ex, $"字体注册失败: {fontNameToUse},继续尝试下一个...");
}
}
}
else if (isWindows)
{
_logger.LogInformation("正在 Windows 系统中查找字体文件...");
foreach (var path in windowsFontPaths)
else
{
if (File.Exists(path))
{
_logger.LogInformation($"找到字体文件: {path}");
fontPathToRegister = path;
if (path.Contains("msyh")) fontNameToUse = "Microsoft YaHei";
else if (path.Contains("simsun")) fontNameToUse = "SimSun";
else if (path.Contains("simhei")) fontNameToUse = "SimHei";
else fontNameToUse = "CustomChineseFont";
break;
}
_logger.LogDebug($"字体文件不存在: {path}");
}
}
// 如果找到了字体文件,尝试显式注册
if (!string.IsNullOrEmpty(fontPathToRegister))
// 如果没有找到字体文件,尝试使用备选字体名称
if (string.IsNullOrEmpty(_selectedChineseFont))
{
try
{
_logger.LogInformation($"正在注册字体: {fontNameToUse} ({fontPathToRegister})");
// 注意:QuestPDF 可能不支持直接从文件路径注册
// 我们采用替代方案:记录字体名称,并在生成时强制使用
_selectedChineseFont = fontNameToUse;
_logger.LogInformation($"字体配置完成: {_selectedChineseFont}");
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"字体注册失败,将尝试自动查找: {fontNameToUse}");
_selectedChineseFont = fontNameToUse; // 仍然记录下来
}
}
else
{
_logger.LogWarning("未找到字体文件,将使用备选字体列表...");
_logger.LogWarning("未找到字体文件,使用备选字体名称列表...");
// 备选字体名称列表
// 备选字体名称列表(按优先级)
var chineseFontNames = new[]
{
"WenQuanYi Zen Hei",
"WenQuanYi Micro Hei",
"Noto Sans CJK SC",
"Microsoft YaHei",
"SimSun",
"SimHei",
"DejaVu Sans"
"WenQuanYi Zen Hei",
"WenQuanYi Micro Hei",
"Noto Sans CJK SC",
"DejaVu Sans",
"Arial Unicode MS",
"PingFang SC",
"STSong"
};
foreach (var fontName in chineseFontNames)
@ -229,8 +213,9 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
break;
}
}
catch
catch (Exception ex)
{
_logger.LogDebug(ex, $"字体不可用: {fontName}");
continue;
}
}
@ -241,6 +226,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
_logger.LogError("请在系统中安装中文字体:");
_logger.LogError(" Ubuntu/Debian: sudo apt-get install fonts-wqy-zenhei fonts-wqy-microhei fonts-noto-cjk");
_logger.LogError(" CentOS/RHEL: sudo yum install wqy-microhei-fonts wqy-zenhei-fonts");
_logger.LogError(" Windows: 确保系统中有微软雅黑或宋体");
}
}
}
@ -1265,7 +1251,7 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
}
/// <summary>
/// 使用QuestPDF生成PDF - 增强版,确保中文字体被正确应用
/// 使用QuestPDF生成PDF - 修复版,确保中文字体被正确应用
/// </summary>
private async Task GeneratePdfWithQuestPDF(Stream pdfStream, DocumentData documentData)
{
@ -1279,16 +1265,42 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
var fontFallbackList = new[]
{
chineseFontName,
"Microsoft YaHei",
"SimSun",
"SimHei",
"WenQuanYi Zen Hei",
"WenQuanYi Micro Hei",
"Noto Sans CJK SC",
"Microsoft YaHei",
"SimSun",
"DejaVu Sans"
"DejaVu Sans",
"Arial Unicode MS"
}.Where(f => !string.IsNullOrEmpty(f)).Distinct().ToArray();
_logger.LogInformation($"字体回退列表: {string.Join(", ", fontFallbackList)}");
// 选择最终要使用的字体(尝试测试每一个)
string finalFont = null;
foreach (var font in fontFallbackList)
{
try
{
if (TestFontAvailability(font))
{
finalFont = font;
_logger.LogInformation($"选择最终字体: {finalFont}");
break;
}
}
catch
{
continue;
}
}
if (string.IsNullOrEmpty(finalFont))
{
_logger.LogWarning("未找到可用字体,将使用 QuestPDF 默认字体");
}
var questDocument = QuestPDF.Fluent.Document.Create(container =>
{
container.Page(page =>
@ -1296,23 +1308,22 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
page.Margin(0);
page.Size(PageSizes.A4);
// 设置默认文本样式 - 强制应用字体
// 设置默认文本样式 - 使用选定的中文字体
page.DefaultTextStyle(x =>
{
var style = x.FontSize(12).FontColor(QuestPDF.Helpers.Colors.Black);
// 尝试应用每一个字体,直到成功
foreach (var font in fontFallbackList)
// 应用选定的字体
if (!string.IsNullOrEmpty(finalFont))
{
try
{
style = style.FontFamily(font);
_logger.LogDebug($"尝试应用字体到默认样式: {font}");
break; // 成功应用一个就停止
style = style.FontFamily(finalFont);
_logger.LogDebug($"应用字体到默认样式: {finalFont}");
}
catch
catch (Exception ex)
{
continue;
_logger.LogWarning(ex, $"应用字体 {finalFont} 失败,使用默认字体");
}
}
@ -1327,11 +1338,9 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
.FitArea();
}
// 使用 Column 来放置所有内容,但禁用默认间距
page.Content().Column(column =>
// 使用绝对定位来放置文本内容(更精确)
page.Content().Layer(layer =>
{
column.Spacing(0);
double? firstParagraphY = null;
double? firstParagraphOriginalY = null;
@ -1366,24 +1375,26 @@ public class CertificateGenerateService : IDynamicApiController, ITransient
_logger.LogDebug("段落 {Index} 位置: X={X}, Y={Y}", i + 1, x, y);
// 使用 Translate 来定位,配合 Spacing(0) 确保没有额外间隔
var textElement = column.Item().TranslateX(x).TranslateY(y).Text(paragraph.Text)
// 使用绝对定位放置文本
var textElement = layer
.TranslateX(x)
.TranslateY(y)
.Text(paragraph.Text)
.FontSize((float)fontSize)
.FontColor(QuestPDF.Helpers.Colors.Black)
.AlignLeft();
// 尝试应用每一个字体到这个文本元素
foreach (var font in fontFallbackList)
// 应用选定的字体
if (!string.IsNullOrEmpty(finalFont))
{
try
{
textElement = textElement.FontFamily(font);
_logger.LogDebug($"段落 {i + 1} 应用字体: {font}");
break;
textElement = textElement.FontFamily(finalFont);
_logger.LogDebug($"段落 {i + 1} 应用字体: {finalFont}");
}
catch
catch (Exception ex)
{
continue;
_logger.LogDebug(ex, $"段落 {i + 1} 应用字体失败");
}
}