2026-03-02 21:09:03 +08:00
|
|
|
|
using YD_Event.Application.Certificate.Dtos;
|
2026-01-17 12:52:58 +08:00
|
|
|
|
using YD_Event.Core.Entities;
|
2026-03-02 21:09:03 +08:00
|
|
|
|
using SkiaSharp;
|
|
|
|
|
|
using QuestPDF.Fluent;
|
|
|
|
|
|
using YD_Event.Core.Certificate;
|
|
|
|
|
|
using YD_Event.Application.File;
|
2025-12-27 12:53:54 +08:00
|
|
|
|
|
|
|
|
|
|
namespace YD_Event.Application.Certificate;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
/// 证书管理服务
|
2025-12-27 12:53:54 +08:00
|
|
|
|
/// </summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
public class CertificateService : BaseService<YD_Event.Core.Entities.Certificate>, ITransient
|
2025-12-27 12:53:54 +08:00
|
|
|
|
{
|
2026-01-17 12:52:58 +08:00
|
|
|
|
private readonly ISqlSugarRepository<YD_Event.Core.Entities.Certificate> _repository;
|
2026-03-02 21:09:03 +08:00
|
|
|
|
private readonly ISqlSugarRepository<EventCertificate> _eventCertRepository;
|
2026-01-17 12:52:58 +08:00
|
|
|
|
private readonly IIdGenerator _idGenerator;
|
2026-03-02 21:09:03 +08:00
|
|
|
|
private readonly FileService _fileService;
|
|
|
|
|
|
private readonly IHttpClientFactory _httpClientFactory;
|
2025-12-27 12:53:54 +08:00
|
|
|
|
|
2026-01-17 12:52:58 +08:00
|
|
|
|
public CertificateService(ISqlSugarRepository<YD_Event.Core.Entities.Certificate> repository,
|
2026-03-02 21:09:03 +08:00
|
|
|
|
ISqlSugarRepository<EventCertificate> eventCertRepository,
|
|
|
|
|
|
IIdGenerator idGenerator,
|
|
|
|
|
|
FileService fileService,
|
|
|
|
|
|
IHttpClientFactory httpClientFactory) : base(repository)
|
2025-12-27 12:53:54 +08:00
|
|
|
|
{
|
2026-01-17 12:52:58 +08:00
|
|
|
|
_repository = repository;
|
2026-03-02 21:09:03 +08:00
|
|
|
|
_eventCertRepository = eventCertRepository;
|
2026-01-17 12:52:58 +08:00
|
|
|
|
_idGenerator = idGenerator;
|
2026-03-02 21:09:03 +08:00
|
|
|
|
_fileService = fileService;
|
|
|
|
|
|
_httpClientFactory = httpClientFactory;
|
2025-12-27 12:53:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
/// 证书分页查询
|
2025-12-27 12:53:54 +08:00
|
|
|
|
/// </summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
[DisplayName("证书分页查询")]
|
2025-12-27 12:53:54 +08:00
|
|
|
|
[HttpGet]
|
2026-01-17 12:52:58 +08:00
|
|
|
|
public async Task<PageResult<CertificateListOutput>> Page([FromQuery] CertificateQueryInput input)
|
2025-12-27 12:53:54 +08:00
|
|
|
|
{
|
2026-01-17 12:52:58 +08:00
|
|
|
|
return await _repository.AsQueryable()
|
2025-12-27 12:53:54 +08:00
|
|
|
|
.WhereIF(!string.IsNullOrWhiteSpace(input.Name), x => x.Name.Contains(input.Name))
|
|
|
|
|
|
.OrderBy(x => x.Sort)
|
2026-01-17 12:52:58 +08:00
|
|
|
|
.OrderByDescending(x => x.CreatedTime)
|
2025-12-27 12:53:54 +08:00
|
|
|
|
.Select(x => new CertificateListOutput
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = x.Id,
|
|
|
|
|
|
Name = x.Name,
|
2026-02-07 16:03:52 +08:00
|
|
|
|
Issuer = x.Issuer,
|
2026-01-17 12:52:58 +08:00
|
|
|
|
TemplateContent = x.TemplateContent,
|
2026-03-02 21:09:03 +08:00
|
|
|
|
TemplateType = x.TemplateType,
|
2025-12-27 12:53:54 +08:00
|
|
|
|
Sort = x.Sort,
|
|
|
|
|
|
CreatedTime = x.CreatedTime
|
2026-01-17 12:52:58 +08:00
|
|
|
|
})
|
|
|
|
|
|
.ToPagedListAsync(input);
|
2025-12-27 12:53:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
/// 添加证书
|
2025-12-27 12:53:54 +08:00
|
|
|
|
/// </summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
[DisplayName("添加证书")]
|
|
|
|
|
|
[HttpPost("add")]
|
|
|
|
|
|
public async Task Add(AddCertificateInput input)
|
2025-12-27 12:53:54 +08:00
|
|
|
|
{
|
2026-01-17 12:52:58 +08:00
|
|
|
|
var entity = input.Adapt<YD_Event.Core.Entities.Certificate>();
|
|
|
|
|
|
entity.Id = _idGenerator.NextId();
|
|
|
|
|
|
await _repository.InsertAsync(entity);
|
2025-12-27 12:53:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 更新证书
|
|
|
|
|
|
/// </summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
[DisplayName("更新证书")]
|
|
|
|
|
|
[HttpPut("edit")]
|
|
|
|
|
|
public async Task Update(UpdateCertificateInput input)
|
2025-12-27 12:53:54 +08:00
|
|
|
|
{
|
2026-01-17 12:52:58 +08:00
|
|
|
|
var entity = await _repository.GetByIdAsync(input.Id);
|
|
|
|
|
|
if (entity == null) throw Oops.Bah("证书不存在");
|
2025-12-27 12:53:54 +08:00
|
|
|
|
|
2026-01-17 12:52:58 +08:00
|
|
|
|
input.Adapt(entity);
|
|
|
|
|
|
await _repository.UpdateAsync(entity);
|
2025-12-27 12:53:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-07 17:28:46 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 删除证书
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("删除证书")]
|
|
|
|
|
|
public async Task Delete(List<long> ids)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ids == null || !ids.Any()) return;
|
|
|
|
|
|
await _repository.DeleteAsync(x => ids.Contains(x.Id));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-27 12:53:54 +08:00
|
|
|
|
/// <summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
/// 获取证书详情
|
2025-12-27 12:53:54 +08:00
|
|
|
|
/// </summary>
|
2026-01-17 12:52:58 +08:00
|
|
|
|
[DisplayName("获取证书详情")]
|
|
|
|
|
|
[HttpGet("detail")]
|
|
|
|
|
|
public async Task<YD_Event.Core.Entities.Certificate> Detail([FromQuery] long id)
|
2025-12-27 12:53:54 +08:00
|
|
|
|
{
|
2026-01-17 12:52:58 +08:00
|
|
|
|
return await _repository.GetByIdAsync(id);
|
2025-12-27 12:53:54 +08:00
|
|
|
|
}
|
2026-03-02 21:09:03 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 上传证书模板并解析
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("上传证书模板")]
|
|
|
|
|
|
[HttpPost("template/upload")]
|
|
|
|
|
|
public async Task<object> UploadTemplate(IFormFile file)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 1. 获取图片尺寸
|
|
|
|
|
|
int width = 0;
|
|
|
|
|
|
int height = 0;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using var stream = file.OpenReadStream();
|
|
|
|
|
|
using var image = SKBitmap.Decode(stream);
|
|
|
|
|
|
if (image != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
width = image.Width;
|
|
|
|
|
|
height = image.Height;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
// 忽略解析错误,可能是PDF
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 上传文件
|
|
|
|
|
|
var uploadResult = await _fileService.Upload(file);
|
|
|
|
|
|
var url = uploadResult.FirstOrDefault()?.Url;
|
|
|
|
|
|
|
|
|
|
|
|
return new
|
|
|
|
|
|
{
|
|
|
|
|
|
Url = url,
|
|
|
|
|
|
Width = width,
|
|
|
|
|
|
Height = height,
|
|
|
|
|
|
// 这里可以添加简单的OCR或表单域识别逻辑,暂留空
|
|
|
|
|
|
DetectedPlaceholders = new List<object>()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成证书预览
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("生成证书预览")]
|
|
|
|
|
|
[HttpPost("generate/preview")]
|
|
|
|
|
|
public async Task<object> GeneratePreview(GenerateCertificateInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var certificate = await _repository.GetByIdAsync(input.CertificateId);
|
|
|
|
|
|
if (certificate == null) throw Oops.Bah("证书配置不存在");
|
|
|
|
|
|
|
|
|
|
|
|
// 下载模板图片
|
|
|
|
|
|
byte[] templateBytes;
|
|
|
|
|
|
using (var client = _httpClientFactory.CreateClient())
|
|
|
|
|
|
{
|
|
|
|
|
|
templateBytes = await client.GetByteArrayAsync(certificate.TemplateContent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成PDF
|
|
|
|
|
|
var document = new CertificateDocument(certificate, input.Data, templateBytes);
|
|
|
|
|
|
var pdfBytes = document.GeneratePdf();
|
|
|
|
|
|
|
|
|
|
|
|
// 这里的返回方式取决于前端需求,可以返回Base64或者上传后返回URL
|
|
|
|
|
|
// 为演示方便,返回Base64
|
|
|
|
|
|
return new
|
|
|
|
|
|
{
|
|
|
|
|
|
PdfBase64 = Convert.ToBase64String(pdfBytes),
|
|
|
|
|
|
FileName = $"{certificate.Name}_{DateTime.Now:yyyyMMddHHmmss}.pdf"
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 批量生成证书
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
[DisplayName("批量生成证书")]
|
|
|
|
|
|
[HttpPost("generate/batch")]
|
|
|
|
|
|
public async Task<object> GenerateBatch(BatchGenerateCertificateInput input)
|
|
|
|
|
|
{
|
|
|
|
|
|
var certificate = await _repository.GetByIdAsync(input.CertificateId);
|
|
|
|
|
|
if (certificate == null) throw Oops.Bah("证书配置不存在");
|
|
|
|
|
|
|
|
|
|
|
|
// 下载模板图片 (只需一次)
|
|
|
|
|
|
byte[] templateBytes;
|
|
|
|
|
|
using (var client = _httpClientFactory.CreateClient())
|
|
|
|
|
|
{
|
|
|
|
|
|
templateBytes = await client.GetByteArrayAsync(certificate.TemplateContent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟批量生成,实际应放入后台任务
|
|
|
|
|
|
var batchId = _idGenerator.NextId();
|
|
|
|
|
|
var results = new List<object>();
|
|
|
|
|
|
var eventCerts = new List<EventCertificate>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var data in input.DataList)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var document = new CertificateDocument(certificate, data, templateBytes);
|
|
|
|
|
|
var pdfBytes = document.GeneratePdf();
|
|
|
|
|
|
|
|
|
|
|
|
// 生成文件名
|
|
|
|
|
|
var certNo = data.ContainsKey("CertificateNo") ? data["CertificateNo"]?.ToString() : _idGenerator.NextId().ToString();
|
|
|
|
|
|
var fileName = $"{certificate.Name}_{certNo}.pdf";
|
|
|
|
|
|
|
|
|
|
|
|
// 上传到OSS
|
|
|
|
|
|
var uploadResult = await _fileService.UploadBytes(pdfBytes, fileName);
|
|
|
|
|
|
|
|
|
|
|
|
// 准备数据库实体
|
|
|
|
|
|
var eventCert = new EventCertificate
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = _idGenerator.NextId(),
|
|
|
|
|
|
CertificateNo = certNo,
|
|
|
|
|
|
CertificateUrl = uploadResult?.Url,
|
|
|
|
|
|
CertificateStatus = 1, // 已生成
|
|
|
|
|
|
GeneratedTime = DateTime.Now,
|
|
|
|
|
|
CreatedTime = DateTime.Now,
|
|
|
|
|
|
DeleteMark = false,
|
|
|
|
|
|
|
|
|
|
|
|
// 尝试从Data中获取关联信息 (约定Key)
|
|
|
|
|
|
ParticipantName = GetString(data, "ParticipantName") ?? GetString(data, "Name"),
|
|
|
|
|
|
GroupName = GetString(data, "GroupName"),
|
|
|
|
|
|
EventId = GetLong(data, "EventId"),
|
|
|
|
|
|
ProjectId = GetLong(data, "ProjectId"),
|
|
|
|
|
|
RegistrationId = GetLong(data, "RegistrationId"),
|
|
|
|
|
|
ResultId = GetLong(data, "ResultId"),
|
|
|
|
|
|
ParticipationRecordId = GetLong(data, "ParticipationRecordId"),
|
|
|
|
|
|
|
|
|
|
|
|
// 其他默认值
|
|
|
|
|
|
CertificateType = 5, // 默认为参与证书,具体逻辑可根据业务调整
|
|
|
|
|
|
AwardLevel = 0
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
eventCerts.Add(eventCert);
|
|
|
|
|
|
results.Add(new { CertificateNo = certNo, Url = uploadResult?.Url, Status = "Success" });
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
results.Add(new { Status = "Failed", Error = ex.Message });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 批量插入数据库
|
|
|
|
|
|
if (eventCerts.Any())
|
|
|
|
|
|
{
|
|
|
|
|
|
await _eventCertRepository.InsertRangeAsync(eventCerts);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return new { BatchId = batchId, Results = results };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private string GetString(Dictionary<string, object> data, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
return data.ContainsKey(key) ? data[key]?.ToString() : null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private long GetLong(Dictionary<string, object> data, string key)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (data.ContainsKey(key) && long.TryParse(data[key]?.ToString(), out long result))
|
|
|
|
|
|
{
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
2025-12-27 12:53:54 +08:00
|
|
|
|
}
|