196 lines
7.0 KiB
C#
196 lines
7.0 KiB
C#
using System;
|
||
using System.IO;
|
||
using QRCoder;
|
||
using Furion.DependencyInjection;
|
||
|
||
namespace YD_Event.Core.Domain.Certificate
|
||
{
|
||
/// <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
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 二维码选项
|
||
/// </summary>
|
||
public class QRCodeOptions
|
||
{
|
||
/// <summary>
|
||
/// 二维码尺寸(像素)
|
||
/// </summary>
|
||
public int Size { get; set; } = 200;
|
||
|
||
/// <summary>
|
||
/// 深色(十六进制颜色代码)
|
||
/// </summary>
|
||
public string DarkColor { get; set; }
|
||
|
||
/// <summary>
|
||
/// 浅色(十六进制颜色代码)
|
||
/// </summary>
|
||
public string LightColor { get; set; }
|
||
|
||
/// <summary>
|
||
/// Logo图片路径
|
||
/// </summary>
|
||
public string LogoPath { get; set; }
|
||
|
||
/// <summary>
|
||
/// 错误纠正级别:L(7%), M(15%), Q(25%), H(30%)
|
||
/// </summary>
|
||
public string ErrorCorrectionLevel { get; set; } = "M";
|
||
|
||
/// <summary>
|
||
/// 是否绘制静区
|
||
/// </summary>
|
||
public bool DrawQuietZones { get; set; } = true;
|
||
}
|
||
}
|