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.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.Op.Method.TaskInteraction.ActionExecutor.Booking; 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 { /// /// 业务邮件发送服务 /// public class MailService : ServiceBase { static readonly ConcurrentDictionary TemplateCache = new ConcurrentDictionary(); IConfiguration config; ApiFox Api = DefaultActionExecutor.Api; /// /// 初始化 /// /// public MailService(IServiceProvider provider) : base(provider) { config = provider.GetRequiredService(); TenantDb.QueryFilter.Clear(); Db.QueryFilter.Clear(); } internal static async Task RenderTemplateAsync(string name, string templateText, object model, IRazorEngine? razorEngine = null) { //int hashCode = name.GetHashCode(); //IRazorEngineCompiledTemplate compiledTemplate = TemplateCache.GetOrAdd(hashCode, i => //{ // var engine = razorEngine ?? new RazorEngine(); // return engine.Compile(templateText); //}); var engine = razorEngine ?? new RazorEngine(); IRazorEngineCompiledTemplate compiledTemplate = engine.Compile(templateText); return await compiledTemplate.RunAsync(model); } /// /// 根据业务ID和类型获取模板所需数据 /// /// /// 业务ID /// 业务类型 /// public static async Task 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> list = []; var order = model.Primary as SeaExportRes; if (order != null) { list.Add(new Tuple(order.SaleId, "sale")); list.Add(new Tuple(order.OperatorId, "op")); list.Add(new Tuple(order.CustomerService, "cs")); list.Add(new Tuple(order.Doc, "doc")); } else { IDictionary? dic = model.Primary as IDictionary; if (dic == null) return model; if (dic.TryGetValue(nameof(SeaExport.SaleId), out object? sale)) list.Add(new Tuple((long)sale, nameof(sale))); if (dic.TryGetValue(nameof(SeaExport.OperatorId), out object? op)) list.Add(new Tuple((long)op, nameof(op))); if (dic.TryGetValue(nameof(SeaExport.CustomerService), out object? cs)) list.Add(new Tuple((long)cs, nameof(cs))); if (dic.TryGetValue(nameof(SeaExport.Doc), out object? doc)) list.Add(new Tuple((long)doc, nameof(doc))); } if (list.Count > 0) { var ids = list.Select(x => x.Item1).ToArray(); var userList = await actionManager.GetUserQueryable(ids).Select(x => new Contact { Id = x.Id, Email = x.Email, EnName = x.UserEnName, Name = x.UserEnName, Mobile = x.Phone, //QQ = x }).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; } /// /// 根据配置发送邮件 /// /// 邮件配置 /// 模板数据 /// public async Task SendAsync(BusinessTaskMail mailConfig, MailTemplateModel templateModel) { ArgumentNullException.ThrowIfNull(mailConfig, nameof(mailConfig)); ArgumentNullException.ThrowIfNull(templateModel, nameof(templateModel)); var order = await TenantDb.Queryable().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().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().FirstAsync(x => x.CreateBy == senderId); if (senderConfig == null) return DataResult.Failed($"发件人用户:{templateModel.Sender.DisplayName} 未设置SMTP发件信息"); templateModel.Sender.MailAddress = senderConfig.MailAccount; List ccList = []; if (mailConfig.CC != null) { //设置抄送人 List 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().Where(x => ccIds.Contains(x.Id)).Select(x => x.Email).ToListAsync(); } //设置收件人 if (mailConfig.Receiver == null) return DataResult.Failed("邮件模板未设置收件人"); List 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"); templateModel.Receivers = await TenantDb.Queryable().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 { string key1 = mailConfig.Id + "-" + nameof(mailConfig.Title); title = await RenderTemplateAsync(key1, mailConfig.Title, templateModel, razorEngine); string key2 = mailConfig.Id + "-" + nameof(mailConfig.Content); content = await RenderTemplateAsync(key2, 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>(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(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 = item.FileName.IsNullOrEmpty() ? Path.GetFileName(reqResult.Data.Data) : item.FileName; var byteArray = await fileResult.Content.ReadAsByteArrayAsync(); string base64Str = Convert.ToBase64String(byteArray); attachmentList.Add(new Tuple(fileName, base64Str)); } } //手工附件 if (templateModel.FileAttachments?.Count > 0) { foreach (var item in templateModel.FileAttachments) { FileInfo file = new FileInfo(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(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)); } } } }