You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

413 lines
18 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using System.Collections.Concurrent;
using DS.Module.Core;
using DS.Module.Core.Data;
using DS.Module.PrintModule;
using DS.WMS.Core.Code.Entity;
using DS.WMS.Core.Info.Entity;
using DS.WMS.Core.Op.Dtos;
using DS.WMS.Core.Op.Dtos.TaskInteraction;
using DS.WMS.Core.Op.Entity;
using DS.WMS.Core.Op.Entity.TaskInteraction;
using DS.WMS.Core.Op.Interface.TaskInteraction;
using DS.WMS.Core.Op.Method.TaskInteraction.ActionExecutor;
using DS.WMS.Core.Sys.Entity;
using Fasterflect;
using HtmlAgilityPack;
using Masuit.Tools;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using RazorEngineCore;
namespace DS.WMS.Core.Op.Method.TaskInteraction
{
/// <summary>
/// 业务邮件发送服务
/// </summary>
public class MailService : ServiceBase
{
static readonly ConcurrentDictionary<string, IRazorEngineCompiledTemplate> TemplateCache = new();
IConfiguration config;
ApiFox Api = DefaultActionExecutor.Api;
/// <summary>
/// 初始化
/// </summary>
/// <param name="provider"></param>
public MailService(IServiceProvider provider) : base(provider)
{
config = provider.GetRequiredService<IConfiguration>();
TenantDb.QueryFilter.Clear<IOrgId>();
Db.QueryFilter.Clear<ITenantId>();
}
//渲染模板
internal static async Task<string> RenderTemplateAsync(string templateText, object model, IRazorEngine? razorEngine = null)
{
if (string.IsNullOrEmpty(templateText))
return templateText;
string crc64 = templateText.Crc64();
IRazorEngineCompiledTemplate compiledTemplate = TemplateCache.GetOrAdd(crc64, i =>
{
var engine = razorEngine ?? new RazorEngine();
return engine.Compile(templateText);
});
return await compiledTemplate.RunAsync(model);
}
/// <summary>
/// 根据业务ID和类型获取模板所需数据
/// </summary>
/// <param name="actionManager"></param>
/// <param name="bsId">业务ID</param>
/// <param name="businessType">业务类型</param>
/// <returns></returns>
public async Task<MailTemplateModel> GetTemplateModelAsync(IActionManagerService actionManager,
long bsId, BusinessType? businessType = null)
{
ArgumentNullException.ThrowIfNull(actionManager, nameof(actionManager));
MailTemplateModel model = new()
{
BusinessId = bsId,
BusinessType = businessType.GetValueOrDefault(),
Primary = await actionManager.GetBusinessDataAsync(bsId, businessType.GetValueOrDefault())
};
if (model.Primary == null)
return model;
List<Tuple<long, string>> list = [];
long? forwarderId = null, shipperCNId = null, customerId = null;
var order = model.Primary as SeaExportRes;
if (order != null)
{
list.Add(new Tuple<long, string>(order.SaleId, "sale"));
list.Add(new Tuple<long, string>(order.OperatorId, "op"));
list.Add(new Tuple<long, string>(order.CustomerService, "cs"));
list.Add(new Tuple<long, string>(order.Doc, "doc"));
forwarderId = order.ForwarderId;
shipperCNId = order.ShipperCnId;
customerId = order.CustomerId;
}
else
{
IDictionary<string, object>? dic = model.Primary as IDictionary<string, object>;
if (dic == null)
return model;
if (dic.TryGetValue(nameof(SeaExport.SaleId), out object? sale))
list.Add(new Tuple<long, string>((long)sale, nameof(sale)));
if (dic.TryGetValue(nameof(SeaExport.OperatorId), out object? op))
list.Add(new Tuple<long, string>((long)op, nameof(op)));
if (dic.TryGetValue(nameof(SeaExport.CustomerService), out object? cs))
list.Add(new Tuple<long, string>((long)cs, nameof(cs)));
if (dic.TryGetValue(nameof(SeaExport.Doc), out object? doc))
list.Add(new Tuple<long, string>((long)doc, nameof(doc)));
if (dic.TryGetValue(nameof(SeaExport.ForwarderId), out object? forwarder) && forwarder != null)
forwarderId = (long)forwarder;
if (dic.TryGetValue(nameof(SeaExport.ShipperCnId), out object? shipperCN) && shipperCN != null)
shipperCNId = (long)shipperCN;
if (dic.TryGetValue(nameof(SeaExport.CustomerId), out object? customer) && customer != null)
customerId = (long)customer;
}
long?[] custIds = [forwarderId, shipperCNId, customerId];
var custList = await TenantDb.Queryable<InfoClient>().Where(x => custIds.Contains(x.Id))
.Select(x => new { x.Id, x.Description }).ToListAsync();
model.ForwarderName = custList.Find(x => x.Id == forwarderId)?.Description;
model.DomesticShipperName = custList.Find(x => x.Id == shipperCNId)?.Description;
model.CustomerName = custList.Find(x => x.Id == customerId)?.Description;
if (list.Count > 0)
{
var ids = list.Select(x => x.Item1).ToArray();
var userList = await Db.Queryable<SysUser>().Where(x => ids.Contains(x.Id)).Select(x => new Contact
{
Id = x.Id,
Code = x.UserCode,
Email = x.Email,
EnName = x.UserEnName,
Name = x.UserName,
Mobile = x.Phone,
Tel = x.OfficePhone,
QQ = x.QQ
}).ToListAsync();
long saleId = list.Find(x => x.Item2 == "sale")!.Item1;
model.Sales = userList.Find(x => x.Id == saleId);
long opId = list.Find(x => x.Item2 == "op")!.Item1;
model.Operator = userList.Find(x => x.Id == opId);
long csId = list.Find(x => x.Item2 == "cs")!.Item1;
model.CustomerService = userList.Find(x => x.Id == csId);
long docId = list.Find(x => x.Item2 == "doc")!.Item1;
model.Document = userList.Find(x => x.Id == docId);
}
return model;
}
/// <summary>
/// 根据配置发送邮件
/// </summary>
/// <param name="mailConfig">邮件配置</param>
/// <param name="templateModel">模板数据</param>
/// <returns></returns>
public async Task<DataResult> SendAsync(BusinessTaskMail mailConfig, MailTemplateModel templateModel)
{
ArgumentNullException.ThrowIfNull(mailConfig, nameof(mailConfig));
ArgumentNullException.ThrowIfNull(templateModel, nameof(templateModel));
var order = await TenantDb.Queryable<SeaExport>().Where(x => x.Id == templateModel.BusinessId)
.Select(x => new
{
x.CustomerNo,
//
x.SaleId,
x.OperatorId,
x.CustomerService,
x.Doc,
//
x.CarrierId,
x.ForwarderId,
x.YardId,
x.TruckerId,
x.CustomerId
}).FirstAsync();
if (order == null)
return DataResult.Failed($"未能获取订单({templateModel.BusinessId})数据");
//设置发件人
if (mailConfig.Sender == null)
return DataResult.Failed("未设置发件人");
long senderId = 0;
if (mailConfig.Sender.IsSale)
senderId = order.SaleId;
else if (mailConfig.Sender.IsOperator)
senderId = order.OperatorId;
else if (mailConfig.Sender.IsCustomerService)
senderId = order.CustomerService;
else if (mailConfig.Sender.IsVouchingClerk)
senderId = order.Doc;
templateModel.Sender = await Db.Queryable<SysUser>().Where(x => x.Id == senderId).Select(x => new MailSender
{
DisplayName = x.UserName,
Phone = x.Phone,
SignatureHtml = x.SignatureHtml
}).FirstAsync();
if (templateModel.Sender == null)
return DataResult.Failed("邮件模板未设置发件人");
var senderConfig = await TenantDb.Queryable<CodeUserEmail>().FirstAsync(x => x.CreateBy == senderId);
if (senderConfig == null)
return DataResult.Failed($"发件人用户:{templateModel.Sender.DisplayName} 未设置SMTP发件信息");
templateModel.Sender.MailAddress = senderConfig.MailAccount;
List<string> ccList = [];
if (mailConfig.CC != null)
{
//设置抄送人
List<long> ccIds = [];
if (mailConfig.CC.IsSale)
ccIds.Add(order.SaleId);
else if (mailConfig.CC.IsOperator)
ccIds.Add(order.OperatorId);
else if (mailConfig.CC.IsCustomerService)
ccIds.Add(order.CustomerService);
else if (mailConfig.CC.IsVouchingClerk)
ccIds.Add(order.Doc);
if (ccIds.Count > 0)
ccList = await Db.Queryable<SysUser>().Where(x => ccIds.Contains(x.Id)).Select(x => x.Email).ToListAsync();
}
//设置收件人
if (mailConfig.Receiver == null)
return DataResult.Failed("邮件模板未设置收件人");
List<string> receiverTypes = [];
if (mailConfig.Receiver.IsCarrier)
receiverTypes.Add("carrier");
if (mailConfig.Receiver.IsBooking)
receiverTypes.Add("booking");
if (mailConfig.Receiver.IsYard)
receiverTypes.Add("yard");
if (mailConfig.Receiver.IsTruck)
receiverTypes.Add("truck");
if (mailConfig.Receiver.IsController)
receiverTypes.Add("controller");
if (mailConfig.Receiver.IsShipper)
receiverTypes.Add("shipper");
if (mailConfig.Receiver.IsShipperCn)
receiverTypes.Add("shippercn");
if (mailConfig.Receiver.IsConsignee)
receiverTypes.Add("consignee");
templateModel.Receivers = await TenantDb.Queryable<BusinessOrderContact>().Where(x => receiverTypes.Contains(x.CustomerType)
&& x.Email != null && x.Email != string.Empty && x.BusinessId == templateModel.BusinessId && x.BusinessType == templateModel.BusinessType)
.Select(x => new MailReceiver { DisplayName = x.Name, MailAddress = x.Email }).ToListAsync();
string title, content = string.Empty;
var razorEngine = new RazorEngine();
try
{
title = await RenderTemplateAsync(mailConfig.Title, templateModel, razorEngine);
content = await RenderTemplateAsync(mailConfig.Content, templateModel, razorEngine);
}
catch (Exception ex)
{
await ex.LogAsync(Db);
return DataResult.Failed($"渲染邮件模板({mailConfig.Id})时出错,请检查模板是否有语法错误");
}
//插入发件人签名
var htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(content);
var node = htmlDoc.GetElementbyId("sign");
if (node != null)
node.InnerHtml = templateModel.Sender.SignatureHtml;
using (StringWriter writer = new())
{
htmlDoc.Save(writer);
content = writer.ToString();
writer.Close();
}
var attachmentList = mailConfig.Attachments == null ? [] : new List<Tuple<string, string>>(mailConfig.Attachments.Count);
try
{
//生成模板附件
if (mailConfig.Attachments?.Count > 0)
{
if (Api.DefaultHeaders.Contains("Authorization"))
Api.DefaultHeaders.Remove("Authorization");
Api.DefaultHeaders.Add("Authorization", "Bearer " + User.GetToken());
long tenantId = long.Parse(User.TenantId);
string requestUrl = config["TaskMail:FileBaseUrl"] + config["TaskMail:SQLPrint"];
foreach (var item in mailConfig.Attachments)
{
object? obj = PropertyExtensions.GetPropertyValue(templateModel, nameof(MailTemplateModel.Primary), Flags.InstancePublic);
var req = new OpenPrintReq
{
ParamJsonStr = JsonConvert.SerializeObject(obj),
PrintType = ((int)FileFormat.PDF).ToString(),
TemplateId = item.TemplateId,
TenantId = tenantId
};
var reqResult = await Api.PostAsync<PrintResult>(requestUrl, req);
if (!reqResult.Data.Succeeded)
return DataResult.Failed($"未能获取打印API生成的文件请求地址{requestUrl}");
string url = config["TaskMail:FileBaseUrl"] + @"/PrintTempFile/" + reqResult.Data.Data;
var fileResult = await Api.SendRequestAsync(HttpMethod.Get, url);
if (!fileResult.IsSuccessStatusCode)
return DataResult.Failed($"未能获取打印API生成的文件附件地址{url}");
string? fileName = Path.GetFileName(reqResult.Data.Data);
if (!string.IsNullOrEmpty(item.FileName)) //设置了文件名
{
if (item.FileName.Contains('@'))
{
fileName = await RenderTemplateAsync(item.FileName, templateModel, razorEngine);
}
else
{
fileName = item.FileName;
}
//文件名的最后一个字符不是“.”,则拼接扩展名
if (item.FileName.LastIndexOf('.') != item.FileName.Length - 1 && item.FileType.HasValue)
fileName = fileName + "." + item.FileType.Value.ToString().ToLowerInvariant();
}
var byteArray = await fileResult.Content.ReadAsByteArrayAsync();
string base64Str = Convert.ToBase64String(byteArray);
attachmentList.Add(new Tuple<string, string>(fileName, base64Str));
}
}
//手工附件
if (templateModel.FileAttachments?.Count > 0)
{
foreach (var item in templateModel.FileAttachments)
{
FileInfo file = new(item.FilePath);
if (!file.Exists)
return DataResult.Failed($"参数设置的附件路径:{file.FullName} 不存在");
using (var stream = file.OpenRead())
{
var byteArray = stream.ToArray();
string base64Str = Convert.ToBase64String(byteArray);
attachmentList.Add(new Tuple<string, string>(item.DisplayName ?? file.Name, base64Str));
}
}
}
string sendTo = string.Join(",", templateModel.Receivers.Select(x => x.MailAddress));
dynamic[] mailParams = [new
{
SendTo = sendTo,
Title = title,
Body = content,
CCTo = string.Join(",", ccList),
ShowName = senderConfig.ShowName.IsNullOrEmpty() ? templateModel.Sender.DisplayName : senderConfig.ShowName,
Account = senderConfig.MailAccount,
senderConfig.Password,
Server = senderConfig.SmtpServer,
Port = senderConfig.SmtpPort.GetValueOrDefault(),
UseSSL = senderConfig.SmtpSSL.GetValueOrDefault(),
Attaches = attachmentList?.Select(x => new
{
AttachName = x.Item1,
AttachContent = x.Item2
}).ToList()
}];
var mailResult = await Api.SendRequestAsync(HttpMethod.Post, config["TaskMail:MailApiUrl"], mailParams);
if (mailResult.IsSuccessStatusCode)
{
return DataResult.Successed($"已发邮件(委托单:{order.CustomerNo}),收件人:"
+ string.Join(",", sendTo) + "发件人:" + senderConfig.MailAccount);
}
return DataResult.Failed($"邮件发送失败API返回状态为{(int)mailResult.StatusCode} {mailResult.StatusCode}"
+ $"(委托单:{order.CustomerNo}),收件人:" + string.Join(",", sendTo) + "发件人:" + senderConfig.MailAccount);
}
catch (Exception ex)
{
await ex.LogAsync(Db);
return DataResult.FailedWithDesc(nameof(MultiLanguageConst.HttpRequestFailed));
}
}
}
}