YD_Event.Admin/YD_Event.Application/Certificate/CertificateTemplateService.cs

456 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using YD_Event.Application.Certificate.Dtos;
using YD_Event.Core.Entities;
using YD_Event.Core.Domain.Certificate;
using SqlSugar;
using Mapster;
using OnceMi.AspNetCore.OSS;
using Microsoft.Extensions.Options;
using YD_Event.Core.Options;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.ComponentModel.DataAnnotations;
namespace YD_Event.Application.Certificate;
/// <summary>
/// 证书模板管理服务
/// </summary>
public class CertificateTemplateService : BaseService<CertificateTemplate>, ITransient
{
private readonly ISqlSugarRepository<CertificateTemplate> _templateRepository;
private readonly ISqlSugarRepository<CertificatePlaceholder> _placeholderRepository;
private readonly IOSSService _ossService;
private readonly IOptionsMonitor<OssConnectionOptions> _ossOptionsMonitor;
private readonly IIdGenerator _idGenerator;
private readonly IWebHostEnvironment _webHostEnvironment;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<CertificateTemplateService> _logger;
private readonly DocxTemplateParser _docxParser;
public CertificateTemplateService(
ISqlSugarRepository<CertificateTemplate> templateRepository,
ISqlSugarRepository<CertificatePlaceholder> placeholderRepository,
IOSSService ossService,
IOptionsMonitor<OssConnectionOptions> ossOptionsMonitor,
IIdGenerator idGenerator,
IWebHostEnvironment webHostEnvironment,
IHttpContextAccessor httpContextAccessor,
ILogger<CertificateTemplateService> logger,
DocxTemplateParser docxParser) : base(templateRepository)
{
_templateRepository = templateRepository;
_placeholderRepository = placeholderRepository;
_ossService = ossService;
_ossOptionsMonitor = ossOptionsMonitor;
_idGenerator = idGenerator;
_webHostEnvironment = webHostEnvironment;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
_docxParser = docxParser;
}
/// <summary>
/// 上传DOC模板
/// </summary>
[DisplayName("上传DOC模板")]
public async Task<CertificateTemplateUploadResult> UploadDocTemplate([Required] IFormFile templateFile)
{
// 1. 验证文件格式
if (!IsValidDocFile(templateFile))
{
throw Oops.Oh("请上传DOC或DOCX格式的文件");
}
// 2. 上传模板文件
var templateUrl = await UploadTemplateFile(templateFile);
// 3. 检测占位符
using var templateStream = templateFile.OpenReadStream();
var placeholders = _docxParser.ExtractPlaceholders(templateStream);
// 4. 创建模板记录(自动填充字段)
var template = new CertificateTemplate
{
Id = _idGenerator.NextId(),
Version = 1, // 自动设置为1
TemplateType = "Doc",
DocxTemplateUrl = templateUrl,
Width = 210, // A4纸宽度(毫米)
Height = 297, // A4纸高度(毫米)
IsActive = true, // 自动设置为激活
Status = Core.Enum.AvailabilityStatus.Enable,
CreatedTime = DateTime.Now,
CreatedUserId = GetCurrentUserUserId()
};
await _templateRepository.InsertAsync(template);
// 5. 保存占位符配置
await SavePlaceholders(template.Id, placeholders);
// 6. 返回结果
var result = new CertificateTemplateUploadResult
{
TemplateId = template.Id,
TemplateUrl = templateUrl,
DetectedPlaceholders = placeholders.Select(p => new DetectedPlaceholderOutput
{
PlaceholderKey = p,
PlaceholderType = _docxParser.GetPlaceholderType(p),
IsValid = _docxParser.IsValidPlaceholder(p),
ValidationMessage = _docxParser.IsValidPlaceholder(p) ? "有效" : "格式无效",
SuggestedDisplayName = GenerateDisplayName(p)
}).ToList(),
TotalPlaceholders = placeholders.Count,
ValidPlaceholders = placeholders.Count(p => _docxParser.IsValidPlaceholder(p))
};
_logger.LogInformation("DOC模板上传成功. TemplateId: {TemplateId}, Placeholders: {Count}",
template.Id, placeholders.Count);
return result;
}
/// <summary>
/// 更新DOC模板
/// </summary>
[DisplayName("更新DOC模板")]
public async Task<CertificateTemplateUploadResult> UpdateDocTemplate(UpdateDocTemplateInput input)
{
var template = await _templateRepository.GetByIdAsync(input.Id);
if (template == null) throw Oops.Bah("模板不存在");
if (template.TemplateType != "Doc")
throw Oops.Bah("只能更新DOC类型的模板");
// 如果上传了新文件
if (input.TemplateFile != null)
{
if (!IsValidDocFile(input.TemplateFile))
{
throw Oops.Oh("请上传DOC或DOCX格式的文件");
}
// 上传新文件
var newTemplateUrl = await UploadTemplateFile(input.TemplateFile);
template.DocxTemplateUrl = newTemplateUrl;
// 重新检测占位符
using var templateStream = input.TemplateFile.OpenReadStream();
var placeholders = _docxParser.ExtractPlaceholders(templateStream);
// 更新占位符配置
await UpdatePlaceholders(template.Id, placeholders);
// 返回检测结果
var result = new CertificateTemplateUploadResult
{
TemplateId = template.Id,
TemplateUrl = newTemplateUrl,
DetectedPlaceholders = placeholders.Select(p => new DetectedPlaceholderOutput
{
PlaceholderKey = p,
PlaceholderType = _docxParser.GetPlaceholderType(p),
IsValid = _docxParser.IsValidPlaceholder(p),
ValidationMessage = _docxParser.IsValidPlaceholder(p) ? "有效" : "格式无效",
SuggestedDisplayName = GenerateDisplayName(p)
}).ToList(),
TotalPlaceholders = placeholders.Count,
ValidPlaceholders = placeholders.Count(p => _docxParser.IsValidPlaceholder(p))
};
}
// 更新其他字段
template.Version = input.Version;
template.Width = input.Width;
template.Height = input.Height;
template.IsActive = input.IsActive;
await _templateRepository.UpdateAsync(template);
_logger.LogInformation("DOC模板更新成功. TemplateId: {TemplateId}", template.Id);
// 返回更新后的模板信息
var updatedPlaceholders = await _placeholderRepository.AsQueryable()
.Where(x => x.TemplateId == template.Id)
.ToListAsync();
return new CertificateTemplateUploadResult
{
TemplateId = template.Id,
TemplateUrl = template.DocxTemplateUrl,
DetectedPlaceholders = updatedPlaceholders.Select(p => new DetectedPlaceholderOutput
{
PlaceholderKey = p.PlaceholderKey,
PlaceholderType = p.PlaceholderType,
IsValid = true,
ValidationMessage = "有效",
SuggestedDisplayName = p.DisplayName
}).ToList(),
TotalPlaceholders = updatedPlaceholders.Count,
ValidPlaceholders = updatedPlaceholders.Count
};
}
/// <summary>
/// 获取模板详情
/// </summary>
[DisplayName("获取模板详情")]
public async Task<CertificateTemplateOutput> Get(IdInput input)
{
var entity = await _templateRepository.GetByIdAsync(input.Id);
if (entity == null) throw Oops.Bah("模板不存在");
var result = entity.Adapt<CertificateTemplateOutput>();
// 获取占位符配置
var placeholders = await _placeholderRepository.AsQueryable()
.Where(x => x.TemplateId == entity.Id)
.OrderBy(x => x.Sort)
.ToListAsync();
result.Placeholders = placeholders.Adapt<List<PlaceholderOutput>>();
return result;
}
/// <summary>
/// 检测DOC模板占位符
/// </summary>
[DisplayName("检测DOC模板占位符")]
public async Task<PlaceholderDetectionResult> DetectPlaceholders(DetectPlaceholdersInput input)
{
if (!IsValidDocFile(input.TemplateFile))
{
throw Oops.Oh("请上传DOC或DOCX格式的文件");
}
var result = new PlaceholderDetectionResult();
try
{
using var templateStream = input.TemplateFile.OpenReadStream();
var placeholders = _docxParser.ExtractPlaceholders(templateStream);
foreach (var placeholder in placeholders)
{
var detected = new DetectedPlaceholderOutput
{
PlaceholderKey = placeholder,
PlaceholderType = _docxParser.GetPlaceholderType(placeholder),
IsValid = _docxParser.IsValidPlaceholder(placeholder),
ValidationMessage = _docxParser.IsValidPlaceholder(placeholder) ? "有效" : "格式无效",
SuggestedDisplayName = GenerateDisplayName(placeholder)
};
result.Placeholders.Add(detected);
if (detected.IsValid)
result.ValidCount++;
else
result.InvalidCount++;
}
_logger.LogInformation("DOC模板占位符检测完成. Total: {Total}, Valid: {Valid}, Invalid: {Invalid}",
result.TotalCount, result.ValidCount, result.InvalidCount);
}
catch (Exception ex)
{
_logger.LogError(ex, "DOC模板占位符检测失败");
throw;
}
return result;
}
/// <summary>
/// 保存占位符配置
/// </summary>
[DisplayName("保存占位符配置")]
public async Task SavePlaceholders(SavePlaceholdersInput input)
{
// 删除现有占位符
await _placeholderRepository.DeleteAsync(x => x.TemplateId == input.TemplateId);
// 添加新占位符
var placeholders = input.Placeholders.Select((p, index) => new CertificatePlaceholder
{
Id = _idGenerator.NextId(),
TemplateId = input.TemplateId,
PlaceholderKey = p.PlaceholderKey,
DisplayName = p.DisplayName,
PlaceholderType = p.PlaceholderType,
DefaultValue = p.DefaultValue,
IsRequired = p.IsRequired,
ValidationRule = p.ValidationRule,
Description = p.Description,
Sort = p.Sort > 0 ? p.Sort : index + 1
}).ToList();
await _placeholderRepository.InsertRangeAsync(placeholders);
_logger.LogInformation("占位符配置保存成功. TemplateId: {TemplateId}, Count: {Count}",
input.TemplateId, placeholders.Count);
}
/// <summary>
/// 获取模板占位符列表
/// </summary>
[DisplayName("获取模板占位符列表")]
public async Task<List<PlaceholderOutput>> GetTemplatePlaceholders(IdInput input)
{
var placeholders = await _placeholderRepository.AsQueryable()
.Where(x => x.TemplateId == input.Id)
.OrderBy(x => x.Sort)
.ToListAsync();
return placeholders.Adapt<List<PlaceholderOutput>>();
}
/// <summary>
/// 删除模板
/// </summary>
[DisplayName("删除模板")]
public async Task DeleteTemplate(DeleteCertificateInput input)
{
if (input.Ids == null || !input.Ids.Any()) return;
// 删除占位符
await _placeholderRepository.DeleteAsync(x => input.Ids.Contains(x.TemplateId));
// 删除模板
await _templateRepository.DeleteAsync(x => input.Ids.Contains(x.Id));
_logger.LogInformation("模板删除成功. Count: {Count}", input.Ids.Count);
}
/// <summary>
/// 验证是否为有效的DOC文件
/// </summary>
private bool IsValidDocFile(IFormFile file)
{
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
return extension == ".doc" || extension == ".docx";
}
/// <summary>
/// 上传模板文件
/// </summary>
private async Task<string> UploadTemplateFile(IFormFile file)
{
var options = _ossOptionsMonitor.CurrentValue;
var now = DateTime.Today;
string name = $"template_{_idGenerator.Encode(_idGenerator.NewLong())}_{file.FileName}";
string filePath = $"/templates/{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}";
}
}
else
{
string bucketPrefix = (options.Provider == OSSProvider.Minio) ? $"/{options.Bucket}" : string.Empty;
string fileName = $"{filePath}{name}";
finalUrl = $"{options.Domain.TrimEnd('/')}{bucketPrefix}{fileName}";
}
using var stream = file.OpenReadStream();
if (!options.Enable)
{
// 本地存储
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);
using var fileStream = new FileStream(fullPath, FileMode.Create, FileAccess.Write);
await stream.CopyToAsync(fileStream);
}
else
{
// OSS 上传
string fileName = $"{filePath}{name}";
await _ossService.PutObjectAsync(options.Bucket, fileName, stream);
}
_logger.LogInformation("模板文件上传成功. URL: {Url}", finalUrl);
return finalUrl;
}
/// <summary>
/// 保存占位符
/// </summary>
private async Task SavePlaceholders(long templateId, List<string> placeholders)
{
var placeholderEntities = placeholders.Select((p, index) => new CertificatePlaceholder
{
Id = _idGenerator.NextId(),
TemplateId = templateId,
PlaceholderKey = p,
DisplayName = GenerateDisplayName(p),
PlaceholderType = _docxParser.GetPlaceholderType(p),
IsRequired = false,
Sort = index + 1
}).ToList();
await _placeholderRepository.InsertRangeAsync(placeholderEntities);
}
/// <summary>
/// 更新占位符
/// </summary>
private async Task UpdatePlaceholders(long templateId, List<string> placeholders)
{
// 删除现有占位符
await _placeholderRepository.DeleteAsync(x => x.TemplateId == templateId);
// 保存新占位符
await SavePlaceholders(templateId, placeholders);
}
/// <summary>
/// 生成显示名称
/// </summary>
private string GenerateDisplayName(string placeholder)
{
return placeholder switch
{
"UserName" => "用户姓名",
"CourseName" => "课程名称",
"CertificateNo" => "证书编号",
"IssueDate" => "颁发日期",
"Organization" => "颁发机构",
"EventName" => "活动名称",
"EventDate" => "活动日期",
"Ranking" => "排名",
"CertificateQR" => "证书二维码",
"VerificationQR" => "验证二维码",
_ => placeholder
};
}
/// <summary>
/// 获取当前用户ID
/// </summary>
private long GetCurrentUserUserId()
{
// 这里需要根据实际的认证机制获取当前用户ID
// 暂时返回1,实际项目中需要实现
return 1;
}
}