2026-03-04 12:13:49 +08:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
using QRCoder;
|
|
|
|
|
|
using Furion.DependencyInjection;
|
2026-04-10 14:29:41 +08:00
|
|
|
|
using YD_Event.Core.Domain.Certificate.Models;
|
2026-03-04 12:13:49 +08:00
|
|
|
|
|
2026-03-14 09:06:43 +08:00
|
|
|
|
namespace YD_Event.Core.Domain.Certificate
|
2026-03-04 12:13:49 +08:00
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 二维码服务
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class QRCodeService : ITransient
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成二维码图片
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="content">二维码内容</param>
|
|
|
|
|
|
/// <param name="size">二维码尺寸(像素)</param>
|
|
|
|
|
|
/// <param name="logoPath">Logo图片路径(可选)</param>
|
|
|
|
|
|
/// <returns>二维码图片流</returns>
|
|
|
|
|
|
public Stream GenerateQRCode(string content, int size = 200, string logoPath = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var qrGenerator = new QRCodeGenerator())
|
|
|
|
|
|
{
|
|
|
|
|
|
var qrCodeData = qrGenerator.CreateQrCode(content, QRCodeGenerator.ECCLevel.M);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用PngByteQRCode来避免Bitmap依赖
|
|
|
|
|
|
var qrCode = new PngByteQRCode(qrCodeData);
|
|
|
|
|
|
var qrCodeBytes = qrCode.GetGraphic(
|
|
|
|
|
|
pixelsPerModule: CalculatePixelsPerModule(size));
|
|
|
|
|
|
|
|
|
|
|
|
var outputStream = new MemoryStream(qrCodeBytes);
|
|
|
|
|
|
outputStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
|
|
return outputStream;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成证书二维码URL
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="certificateId">证书ID</param>
|
|
|
|
|
|
/// <param name="baseUrl">基础URL</param>
|
|
|
|
|
|
/// <returns>证书查看URL</returns>
|
|
|
|
|
|
public string GenerateCertificateUrl(long certificateId, string baseUrl)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
|
|
|
|
|
throw new ArgumentException("Base URL cannot be null or empty", nameof(baseUrl));
|
|
|
|
|
|
|
|
|
|
|
|
var normalizedBaseUrl = baseUrl.TrimEnd('/');
|
|
|
|
|
|
return $"{normalizedBaseUrl}/certificate/view/{certificateId}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成证书PDF下载URL
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="certificateId">证书ID</param>
|
|
|
|
|
|
/// <param name="pdfUrl">PDF文件URL</param>
|
|
|
|
|
|
/// <returns>PDF下载URL</returns>
|
|
|
|
|
|
public string GeneratePdfUrl(long certificateId, string pdfUrl)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(pdfUrl))
|
|
|
|
|
|
throw new ArgumentException("PDF URL cannot be null or empty", nameof(pdfUrl));
|
|
|
|
|
|
|
|
|
|
|
|
return pdfUrl;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成带验证信息的二维码内容
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="certificateId">证书ID</param>
|
|
|
|
|
|
/// <param name="verificationCode">验证码</param>
|
|
|
|
|
|
/// <param name="baseUrl">基础URL</param>
|
|
|
|
|
|
/// <returns>验证URL</returns>
|
|
|
|
|
|
public string GenerateVerificationUrl(long certificateId, string verificationCode, string baseUrl)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(baseUrl))
|
|
|
|
|
|
throw new ArgumentException("Base URL cannot be null or empty", nameof(baseUrl));
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(verificationCode))
|
|
|
|
|
|
throw new ArgumentException("Verification code cannot be null or empty", nameof(verificationCode));
|
|
|
|
|
|
|
|
|
|
|
|
var normalizedBaseUrl = baseUrl.TrimEnd('/');
|
|
|
|
|
|
return $"{normalizedBaseUrl}/certificate/verify/{certificateId}?code={verificationCode}";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 计算每个模块的像素数
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="desiredSize">期望的二维码尺寸</param>
|
|
|
|
|
|
/// <returns>每个模块的像素数</returns>
|
|
|
|
|
|
private int CalculatePixelsPerModule(int desiredSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 标准二维码是25x25或21x21模块,这里按25计算
|
|
|
|
|
|
const int standardModules = 25;
|
|
|
|
|
|
return Math.Max(1, desiredSize / standardModules);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 验证二维码内容
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="content">二维码内容</param>
|
|
|
|
|
|
/// <returns>是否有效</returns>
|
|
|
|
|
|
public bool ValidateQRContent(string content)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(content))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查内容长度(二维码有容量限制)
|
|
|
|
|
|
if (content.Length > 2953) // 数字模式的最大容量
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否为有效URL或文本
|
|
|
|
|
|
return Uri.TryCreate(content, UriKind.Absolute, out _) || !content.Contains(" ");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 生成带样式的二维码
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="content">二维码内容</param>
|
|
|
|
|
|
/// <param name="options">二维码选项</param>
|
|
|
|
|
|
/// <returns>二维码图片流</returns>
|
|
|
|
|
|
public Stream GenerateStyledQRCode(string content, QRCodeOptions options)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var qrGenerator = new QRCodeGenerator())
|
|
|
|
|
|
{
|
|
|
|
|
|
var eccLevel = GetECCLevel(options.ErrorCorrectionLevel);
|
|
|
|
|
|
var qrCodeData = qrGenerator.CreateQrCode(content, eccLevel);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用PngByteQRCode来避免Bitmap依赖
|
|
|
|
|
|
var qrCode = new PngByteQRCode(qrCodeData);
|
|
|
|
|
|
var pixelsPerModule = options.Size > 0 ? CalculatePixelsPerModule(options.Size) : 10;
|
|
|
|
|
|
|
|
|
|
|
|
var qrCodeBytes = qrCode.GetGraphic(pixelsPerModule: pixelsPerModule);
|
|
|
|
|
|
|
|
|
|
|
|
var outputStream = new MemoryStream(qrCodeBytes);
|
|
|
|
|
|
outputStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
|
|
return outputStream;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取错误纠正级别
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="level">级别字符串</param>
|
|
|
|
|
|
/// <returns>ECC级别</returns>
|
|
|
|
|
|
private QRCodeGenerator.ECCLevel GetECCLevel(string level)
|
|
|
|
|
|
{
|
|
|
|
|
|
return level?.ToUpperInvariant() switch
|
|
|
|
|
|
{
|
|
|
|
|
|
"L" => QRCodeGenerator.ECCLevel.L,
|
|
|
|
|
|
"M" => QRCodeGenerator.ECCLevel.M,
|
|
|
|
|
|
"Q" => QRCodeGenerator.ECCLevel.Q,
|
|
|
|
|
|
"H" => QRCodeGenerator.ECCLevel.H,
|
|
|
|
|
|
_ => QRCodeGenerator.ECCLevel.M
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|