Files

228 lines
9.3 KiB
C#

using Model;
using Model.Dto.Asset;
using Model.Entity.Asset;
using ORM;
using QRCoder;
using Service.Interface;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Service.Implement
{
/// <summary>
/// 设备二维码 服务实现
/// 扫码直达 URL 约定:{baseUrl}/asset/ledger/equipment?id={equipmentId}
/// baseUrl 由 Controller 从 HTTP 请求(scheme://host)传入;如需固定域名可改为读 sys_param 的 site_base_url
/// </summary>
public class QrCodeService : IQrCodeService
{
public async Task<Result<QrCodeDto>> GenerateAsync(long equipmentId, string baseUrl)
{
if (equipmentId <= 0) return Result<QrCodeDto>.Error("设备Id无效");
try
{
var entity = await LoadEquipmentAsync(equipmentId);
if (entity == null) return Result<QrCodeDto>.Error("设备不存在或已被删除");
var url = BuildQrUrl(baseUrl, equipmentId);
if (string.IsNullOrEmpty(entity.QrCodeUrl))
{
await SqlSugarContext.DbContext.Updateable<EquipmentEntity>()
.SetColumns(x => x.QrCodeUrl == url)
.Where(x => x.Id == equipmentId).ExecuteCommandAsync();
entity.QrCodeUrl = url;
}
return Result<QrCodeDto>.Success(ToDto(entity));
}
catch (Exception ex)
{
return Result<QrCodeDto>.Error("生成二维码失败", ex);
}
}
public async Task<Result<byte[]>> GenerateImageAsync(long equipmentId, string baseUrl)
{
if (equipmentId <= 0) return Result<byte[]>.Error("设备Id无效");
try
{
var entity = await LoadEquipmentAsync(equipmentId);
if (entity == null) return Result<byte[]>.Error("设备不存在或已被删除");
var url = string.IsNullOrEmpty(entity.QrCodeUrl) ? BuildQrUrl(baseUrl, equipmentId) : entity.QrCodeUrl;
if (string.IsNullOrEmpty(entity.QrCodeUrl))
{
await SqlSugarContext.DbContext.Updateable<EquipmentEntity>()
.SetColumns(x => x.QrCodeUrl == url)
.Where(x => x.Id == equipmentId).ExecuteCommandAsync();
entity.QrCodeUrl = url;
}
var png = EncodePng(url);
return Result<byte[]>.Success(png);
}
catch (Exception ex)
{
return Result<byte[]>.Error("生成二维码图片失败", ex);
}
}
public async Task<Result<List<QrCodeBatchResultDto>>> BatchGenerateAsync(long[] equipmentIds, string baseUrl)
{
if (equipmentIds == null || equipmentIds.Length == 0)
return Result<List<QrCodeBatchResultDto>>.Error("设备Id列表不能为空");
var results = new List<QrCodeBatchResultDto>();
foreach (var id in equipmentIds.Distinct())
{
var r = await GenerateAsync(id, baseUrl);
results.Add(new QrCodeBatchResultDto
{
EquipmentId = id.ToString(),
EquipmentCode = r.Data?.EquipmentCode,
EquipmentName = r.Data?.EquipmentName,
QrCodeUrl = r.Data?.QrCodeUrl,
Success = r.IsSuccess,
Message = r.IsSuccess ? "成功" : r.Msg
});
}
return Result<List<QrCodeBatchResultDto>>.Success(results);
}
public async Task<Result<List<QrCodeDto>>> GetListPagedAsync(int pageIndex, int pageSize, RefAsync<int> total, string? keyword = null, bool qrOnly = false)
{
try
{
var list = await SqlSugarContext.DbContext.Queryable<EquipmentEntity>()
.Where(x => x.IsDel == 0)
.WhereIF(!string.IsNullOrWhiteSpace(keyword), x => x.Code!.Contains(keyword!) || x.Name!.Contains(keyword!))
.WhereIF(qrOnly, x => x.QrCodeUrl != null && x.QrCodeUrl != "")
.OrderBy(x => x.Code)
.ToPageListAsync(pageIndex, pageSize, total);
return Result<List<QrCodeDto>>.Success(list.Select(ToDto).ToList());
}
catch (Exception ex)
{
return Result<List<QrCodeDto>>.Error("查询二维码列表失败", ex);
}
}
public async Task<Result<string>> GetPrintHtmlAsync(long equipmentId, string baseUrl)
{
if (equipmentId <= 0) return Result<string>.Error("设备Id无效");
try
{
var entity = await LoadEquipmentAsync(equipmentId);
if (entity == null) return Result<string>.Error("设备不存在或已被删除");
var url = string.IsNullOrEmpty(entity.QrCodeUrl) ? BuildQrUrl(baseUrl, equipmentId) : entity.QrCodeUrl;
var png = EncodePng(url);
var base64 = Convert.ToBase64String(png);
var html = BuildPrintHtml(entity, url, base64);
return Result<string>.Success(html);
}
catch (Exception ex)
{
return Result<string>.Error("生成打印页失败", ex);
}
}
public async Task<Result> BindRfidAsync(long equipmentId, string rfidCode)
{
if (equipmentId <= 0) return Result.Error("设备Id无效");
if (string.IsNullOrWhiteSpace(rfidCode)) return Result.Error("RFID 编号不能为空");
try
{
var exists = await SqlSugarContext.DbContext.Queryable<EquipmentEntity>()
.Where(x => x.Id == equipmentId && x.IsDel == 0).AnyAsync();
if (!exists) return Result.Error("设备不存在或已被删除");
await SqlSugarContext.DbContext.Updateable<EquipmentEntity>()
.SetColumns(x => x.RfidCode == rfidCode)
.Where(x => x.Id == equipmentId).ExecuteCommandAsync();
return Result.Success();
}
catch (Exception ex)
{
return Result.Error("绑定 RFID 失败", ex);
}
}
// ==================== 私有工具 ====================
private static async Task<EquipmentEntity?> LoadEquipmentAsync(long id)
=> await SqlSugarContext.DbContext.Queryable<EquipmentEntity>()
.Where(x => x.Id == id && x.IsDel == 0).FirstAsync();
private static QrCodeDto ToDto(EquipmentEntity e) => new()
{
EquipmentId = e.Id.ToString(),
EquipmentCode = e.Code,
EquipmentName = e.Name,
Location = e.Location,
QrCode = e.QrCode,
QrCodeUrl = e.QrCodeUrl,
RfidCode = e.RfidCode
};
private static string BuildQrUrl(string baseUrl, long equipmentId)
{
var b = (baseUrl ?? string.Empty).TrimEnd('/');
return $"{b}/asset/ledger/equipment?id={equipmentId}";
}
private static byte[] EncodePng(string content)
{
using var gen = new QRCodeGenerator();
var data = gen.CreateQrCode(content, QRCodeGenerator.ECCLevel.Q);
var png = new PngByteQRCode(data);
return png.GetGraphic(20); // 20 px per module,便于扫描识别
}
private static string BuildPrintHtml(EquipmentEntity e, string url, string base64)
{
// 简单 A4 打印模板:左侧二维码,右侧设备信息;浏览器 window.print() 即可
var safe = (string? s) => System.Net.WebUtility.HtmlEncode(s ?? string.Empty);
return $@"<!DOCTYPE html>
<html lang='zh-CN'>
<head>
<meta charset='utf-8' />
<title>设备二维码 - {safe(e.Name)}</title>
<style>
@page {{ size: A4; margin: 12mm; }}
body {{ font-family: 'Microsoft YaHei', sans-serif; color: #303133; }}
.label {{ border:1px solid #303133; border-radius:6px; padding:16px; display:flex; gap:24px; align-items:center; max-width:480px; }}
.qr img {{ width: 220px; height: 220px; }}
.info dl {{ margin:0; }}
.info dt {{ font-size:13px; color:#909399; margin-top:8px; }}
.info dd {{ font-size:16px; margin:4px 0 0 0; font-weight:600; }}
.info h2 {{ margin:0 0 8px 0; font-size:20px; }}
.url {{ margin-top:12px; font-size:12px; color:#909399; word-break:break-all; }}
.actions {{ margin-top:16px; }}
.actions button {{ padding:6px 16px; font-size:14px; cursor:pointer; }}
</style>
</head>
<body>
<div class='label'>
<div class='qr'><img src='data:image/png;base64,{base64}' alt='QR' /></div>
<div class='info'>
<h2>{safe(e.Name)}</h2>
<dl>
<dt>设备编号</dt><dd>{safe(e.Code)}</dd>
<dt>二维码编号</dt><dd>{safe(e.QrCode)}</dd>
<dt>存放位置</dt><dd>{safe(e.Location)}</dd>
<dt>责任人</dt><dd>{safe(e.ResponsiblePerson)}</dd>
</dl>
<div class='url'>{safe(url)}</div>
</div>
</div>
<div class='actions'><button onclick='window.print()'>打印</button></div>
<script>window.onload=function(){{setTimeout(function(){{window.print();}},300);}};</script>
</body>
</html>";
}
}
}