YD_Event.Admin/YD_Event.Application/Certificate/CertificateGenerateService.cs
2026-03-03 16:46:02 +08:00

228 lines
8.7 KiB
C#

using YD_Event.Application.Certificate.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Options;
using SqlSugar;
using Mapster;
using OnceMi.AspNetCore.OSS;
using Microsoft.Extensions.Options;
using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace YD_Event.Application.Certificate;
/// <summary>
/// 证书生成服务
/// </summary>
public class CertificateGenerateService : IDynamicApiController, ITransient
{
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Certificate> _certificateRepository;
private readonly ISqlSugarRepository<CertificateTemplate> _templateRepository;
private readonly ISqlSugarRepository<CertificateElement> _elementRepository;
private readonly IOSSService _ossService;
private readonly IOptionsMonitor<OssConnectionOptions> _ossOptionsMonitor;
private readonly IIdGenerator _idGenerator;
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<CertificateGenerateService> _logger;
public CertificateGenerateService(
ISqlSugarRepository<YD_Event.Core.Entities.Certificate> certificateRepository,
ISqlSugarRepository<CertificateTemplate> templateRepository,
ISqlSugarRepository<CertificateElement> elementRepository,
IOSSService ossService,
IOptionsMonitor<OssConnectionOptions> ossOptionsMonitor,
IIdGenerator idGenerator,
IWebHostEnvironment webHostEnvironment,
IHttpContextAccessor httpContextAccessor,
ILogger<CertificateGenerateService> logger)
{
_certificateRepository = certificateRepository;
_templateRepository = templateRepository;
_elementRepository = elementRepository;
_ossService = ossService;
_ossOptionsMonitor = ossOptionsMonitor;
_idGenerator = idGenerator;
_webHostEnvironment = webHostEnvironment;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
/// <summary>
/// 生成证书
/// </summary>
[DisplayName("生成证书")]
public async Task<string> Generate(GenerateCertificateInput input)
{
// 1. 获取证书信息
var certificate = await _certificateRepository.GetByIdAsync(input.CertificateId);
if (certificate == null) throw Oops.Bah("证书不存在");
if (certificate.Status != Core.Enum.AvailabilityStatus.Enable) throw Oops.Bah("证书已禁用");
// 2. 获取模板 (优先使用 input 指定的,否则使用证书关联的)
CertificateTemplate template;
if (input.TemplateId.HasValue)
{
template = await _templateRepository.GetByIdAsync(input.TemplateId.Value);
}
else
{
// 使用证书关联的模板
template = await _templateRepository.GetByIdAsync(certificate.TemplateId);
}
if (template == null) throw Oops.Bah("未找到有效证书模板");
// 3. 获取元素
var elements = await _elementRepository.AsQueryable()
.Where(x => x.TemplateId == template.Id)
.OrderBy(x => x.Sort)
.ToListAsync();
// 4. 生成 PDF 内容
byte[] bgImageBytes;
using (var httpClient = new HttpClient())
{
bgImageBytes = await httpClient.GetByteArrayAsync(template.BackgroundImageUrl);
}
// 预生成文件名和路径,以便在生成 PDF 时使用 URL (如二维码)
var options = _ossOptionsMonitor.CurrentValue;
var now = DateTime.Today;
string name = _idGenerator.Encode(_idGenerator.NewLong());
string extension = ".pdf";
string filePath = $"/{now.Year}/{now.Month:D2}/{now.Day:D2}/";
string finalUrl = string.Empty;
// 预测最终 URL
if (!options.Enable)
{
string localDir = string.Concat(options.Bucket.TrimEnd('/'), filePath);
var request = _httpContextAccessor.HttpContext?.Request;
if (request != null)
{
finalUrl = $"{request.Scheme}://{request.Host.Value}/{localDir}{name}{extension}";
}
}
else
{
string bucketPrefix = (options.Provider == OSSProvider.Minio) ? $"/{options.Bucket}" : string.Empty;
string fileName = $"{filePath}{name}{extension}";
finalUrl = $"{options.Domain.TrimEnd('/')}{bucketPrefix}{fileName}";
}
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(new PageSize(template.Width, template.Height));
page.Margin(0);
page.Content().Layers(layers =>
{
layers.PrimaryLayer().Image(bgImageBytes).FitArea();
foreach (var element in elements)
{
DrawElement(layers, element, input.Data, finalUrl);
}
});
});
});
var pdfBytes = document.GeneratePdf();
// 5. 文件处理逻辑 (参照 FileService)
if (!options.Enable)
{
// 本地存储
_logger.LogInformation("开始本地存储证书文件. Bucket: {Bucket}, Path: {Path}", options.Bucket, filePath);
string localDir = string.Concat(options.Bucket.TrimEnd('/'), filePath);
string absoluteDir = Path.Combine(_webHostEnvironment.WebRootPath, localDir);
if (!Directory.Exists(absoluteDir))
{
Directory.CreateDirectory(absoluteDir);
}
string fullPath = Path.Combine(absoluteDir, $"{name}{extension}");
await System.IO.File.WriteAllBytesAsync(fullPath, pdfBytes);
_logger.LogInformation("证书文件本地存储成功. URL: {Url}", finalUrl);
}
else
{
// OSS 上传
_logger.LogInformation("开始上传证书文件到 OSS. Bucket: {Bucket}, Path: {Path}", options.Bucket, filePath);
string fileName = $"{filePath}{name}{extension}";
using (var stream = new MemoryStream(pdfBytes))
{
await _ossService.PutObjectAsync(options.Bucket, fileName, stream);
}
_logger.LogInformation("证书文件 OSS 上传成功. URL: {Url}", finalUrl);
}
return finalUrl;
}
private void DrawElement(LayersDescriptor layers, CertificateElement element, Dictionary<string, string> data, string qrCodeUrl)
{
float x = element.X;
float y = element.Y;
float w = element.Width;
float h = element.Height;
var layer = layers.Layer()
.TranslateX(x)
.TranslateY(y)
.Width(w)
.Height(h);
if (element.Type == "Text")
{
string text = element.FieldName;
if (!string.IsNullOrEmpty(element.BindingKey) && data != null && data.ContainsKey(element.BindingKey))
{
text = data[element.BindingKey];
}
var isBold = element.StyleJson != null && element.StyleJson.Contains("\"bold\":true", StringComparison.OrdinalIgnoreCase);
var isCenter = element.StyleJson != null && element.StyleJson.Contains("\"align\":\"center\"", StringComparison.OrdinalIgnoreCase);
var isRight = element.StyleJson != null && element.StyleJson.Contains("\"align\":\"right\"", StringComparison.OrdinalIgnoreCase);
var container = layer;
if (isCenter) container = container.AlignCenter();
else if (isRight) container = container.AlignRight();
else container = container.AlignLeft();
container.Text(t =>
{
var span = t.Span(text)
.FontFamily(element.FontFamily)
.FontSize(element.FontSize)
.FontColor(element.Color ?? "#000000");
if (isBold) span.Bold();
// Fallback
#pragma warning disable CS0618
span.Fallback(x => x.FontFamily("SimHei"));
#pragma warning restore CS0618
});
}
else if (element.Type == "QRCode")
{
string content = qrCodeUrl;
if (!string.IsNullOrEmpty(element.BindingKey) && data != null && data.ContainsKey(element.BindingKey))
{
content = data[element.BindingKey];
}
// layer.Element(e => e.QrCode(content));
layer.Text("QR: " + content);
}
}
}