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.

470 lines
20 KiB
C#

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.Entity;
using DS.WMS.Core.Sys.Entity;
using DS.WMS.Core.TaskInteraction.Dtos;
using DS.WMS.Core.TaskInteraction.Entity;
using DS.WMS.Core.TaskInteraction.Interface;
using DS.WMS.Core.TaskInteraction.Method.ActionExecutor;
using DS.WMS.Core.TaskInteraction.Method.DataProvider;
using Fasterflect;
using HtmlAgilityPack;
using Masuit.Tools;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using RazorEngineCore;
namespace DS.WMS.Core.TaskInteraction.Method
{
/// <summary>
/// 邮件模板发送
/// </summary>
public class MailGenerator : ServiceBase
{
static readonly ConcurrentDictionary<string, IRazorEngineCompiledTemplate> TemplateCache = new();
readonly IConfiguration config;
readonly ApiFox Api = DefaultActionExecutor.Api;
/// <summary>
/// 初始化
/// </summary>
/// <param name="provider"></param>
public MailGenerator(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="taskMail">邮件配置</param>
/// <param name="bsId">业务ID</param>
/// <param name="bsType">业务类型</param>
/// <param name="actionManager"></param>
/// <returns></returns>
public async Task<MailTemplateModel> GetBusinessModelAsync(BusinessTaskMail taskMail,
long bsId, BusinessType? bsType = null, IActionManagerService? actionManager = null)
{
ArgumentNullException.ThrowIfNull(taskMail, nameof(taskMail));
MailTemplateModel model = new()
{
BusinessId = bsId,
BusinessType = bsType
};
if (taskMail.Provider == null)
{
actionManager ??= ServiceProvider.GetRequiredService<IActionManagerService>();
model.Primary = await actionManager.GetBusinessDataAsync(bsId, bsType.GetValueOrDefault());
}
else
{
IDataProvider? dataProvider = null;
switch (taskMail.Provider.Type)
{
case DataProviderType.Custom:
if (string.IsNullOrEmpty(taskMail.Provider.TypeName))
return model;
var type = Type.GetType(taskMail.Provider.TypeName, true);
dataProvider = ConstructorExtensions.CreateInstance(type, [ServiceProvider]) as IDataProvider;
dataProvider ??= ConstructorExtensions.CreateInstance(type) as IDataProvider;
break;
case DataProviderType.Fixed:
model.Primary = JsonConvert.DeserializeObject<dynamic>(taskMail.Content!);
break;
case DataProviderType.Database:
dataProvider = new DefaultDatabaseProvider(ServiceProvider);
break;
case DataProviderType.Network:
break;
}
if (dataProvider != null)
{
DataFetchContext context = new()
{
ServiceProvider = ServiceProvider,
BusinessId = bsId,
BusinessType = bsType,
Content = taskMail.Content,
};
await dataProvider.FetchDataAsync(context);
model.Primary = context.Data;
}
}
if (model.Primary == null)
return model;
List<Tuple<long, string>> list = [];
long? forwarderId = null, shipperCNId = null, customerId = null;
if (model.Primary is SeaExportRes order)
{
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
{
if (model.Primary is not IDictionary<string, object> dic)
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);
}
//获取箱型价格
model.CtnPriceList = await TenantDb.Queryable<BusinessCtnPrice>().Where(x => x.BusinessId == model.BusinessId)
.Select(x => new BusinessCtnPrice
{
Ctn = x.Ctn,
CtnAll = x.CtnAll,
CtnNum = x.CtnNum,
QuotePrice = x.QuotePrice,
GuidePrice = x.GuidePrice,
FloorPrice = x.FloorPrice
}).ToListAsync();
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 = templateModel.GetPropertyValue(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 response = await Api.SendRequestAsync(HttpMethod.Get, url);
if (!response.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 response.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));
}
}
}
}