|
|
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));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
}
|