using Furion; using Furion.DynamicApiController; using Furion.FriendlyException; using Furion.JsonSerialization; using Furion.RemoteRequest.Extensions; using HtmlAgilityPack; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Myshipping.Application.ConfigOption; using Myshipping.Application.Entity; using Myshipping.Core; using Myshipping.Core.Entity; using Myshipping.Core.Service; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; namespace Myshipping.Application { /// /// DRAFT任务 /// [ApiDescriptionSettings("Application", Name = "TaskManageDRAFT", Order = 10)] public class TaskManageDRAFTService: ITaskManageDRAFTService, IDynamicApiController { private readonly ISysCacheService _cache; private readonly ILogger _logger; private readonly SqlSugarRepository _taskBaseRepository; private readonly SqlSugarRepository _taskFileRepository; private readonly SqlSugarRepository _taskDraftInfoRepository; private readonly SqlSugarRepository _djyUserMailAccount; private readonly SqlSugarRepository _bookingOrderContactRepository; private readonly SqlSugarRepository _bookingOrderRepository; private readonly SqlSugarRepository _sysUserRepository; private readonly IDjyCustomerService _djyCustomerService; public TaskManageDRAFTService(SqlSugarRepository taskBaseRepository, SqlSugarRepository taskFileRepository, SqlSugarRepository taskDraftInfoRepository, SqlSugarRepository djyUserMailAccount, SqlSugarRepository bookingOrderContactRepository, SqlSugarRepository bookingOrderRepository, SqlSugarRepository sysUserRepository, IDjyCustomerService djyCustomerService, ISysCacheService cache, ILogger logger) { _taskBaseRepository = taskBaseRepository; _taskFileRepository = taskFileRepository; _taskDraftInfoRepository = taskDraftInfoRepository; _bookingOrderRepository = bookingOrderRepository; _bookingOrderContactRepository = bookingOrderContactRepository; _djyUserMailAccount = djyUserMailAccount; _djyCustomerService = djyCustomerService; _sysUserRepository = sysUserRepository; _logger = logger; _cache = cache; } #region 获取DRAFT详情 /// /// 获取DRAFT详情 /// /// DRAFT主键 /// 返回回执 [HttpGet("/TaskManageDRAFT/GetInfo")] public async Task GetInfo(string pkId) { TaskDraftShowDto dto = new TaskDraftShowDto(); var draft = _taskDraftInfoRepository.AsQueryable().First(a => a.PK_ID == pkId); if (draft == null) throw Oops.Oh($"DRAFT主键{pkId}无法获取业务信息"); var taskBase = _taskBaseRepository.AsQueryable().First(a => a.PK_ID == draft.TASK_ID); if (taskBase == null) throw Oops.Oh($"任务主键无法获取业务信息"); dto = new TaskDraftShowDto { PKId = draft.PK_ID, TaskId = draft.TASK_ID, Carrier = draft.CARRIER, BookingId = draft.BOOKING_ID, MBlNo = draft.MBL_NO, NoticeDate = draft.NOTICE_DATE, }; if (dto != null) { dto.IsComplete = taskBase.IS_COMPLETE == 1 ? true : false; dto.CompleteTime = taskBase.COMPLETE_DATE; } return dto; } #endregion #region 通过任务主键获取DRAFT详情 /// /// 通过任务主键获取DRAFT详情 /// /// DRAFT任务主键 /// 返回回执 [HttpGet("/TaskManageDRAFT/GetInfoByTaskId")] public async Task GetInfoByTaskId(string taskPkId) { TaskDraftShowDto dto = new TaskDraftShowDto(); var taskBase = _taskBaseRepository.AsQueryable().First(a => a.PK_ID == taskPkId); if (taskBase == null) throw Oops.Oh($"任务主键{taskPkId}无法获取业务信息"); var draft = _taskDraftInfoRepository.AsQueryable().First(a => a.TASK_ID == taskBase.PK_ID); if (draft == null) throw Oops.Oh($"DRAFT主键{taskPkId}无法获取业务信息"); dto = new TaskDraftShowDto { PKId = draft.PK_ID, TaskId = draft.TASK_ID, Carrier = draft.CARRIER, BookingId = draft.BOOKING_ID, MBlNo = draft.MBL_NO, NoticeDate = draft.NOTICE_DATE, }; if (dto != null) { dto.IsComplete = taskBase.IS_COMPLETE == 1 ? true : false; dto.CompleteTime = taskBase.COMPLETE_DATE; } return dto; } #endregion #region 任务ID下载附件 /// /// 任务ID下载附件 /// /// DRAFT任务主键 /// 附件分类代码 /// 返回数据流 [HttpGet("/TaskManageDRAFT/DownloadFile")] public async Task DownloadFile([FromQuery] string taskPKId, [FromQuery] string fileCategory = "DRAFT") { var bcTaskInfo = await _taskBaseRepository.AsQueryable().FirstAsync(u => u.PK_ID == taskPKId); if (bcTaskInfo == null) { throw Oops.Oh($"任务主键{taskPKId}无法获取业务信息"); } TaskFileCategoryEnum fileCategoryEnum = TaskFileCategoryEnum.NONE; System.Enum.TryParse(fileCategory, out fileCategoryEnum); if (fileCategoryEnum == TaskFileCategoryEnum.NONE) { throw Oops.Oh($"附件分类代码错误,请提供正确的分类代码"); } string name = fileCategoryEnum.ToString(); var fileInfo = await _taskFileRepository.AsQueryable().FirstAsync(u => u.TASK_PKID == taskPKId && u.FILE_CATEGORY == name); if (fileInfo == null) { throw Oops.Oh($"任务主键{taskPKId}没有可下载的附件"); } var opt = App.GetOptions(); var dirAbs = opt.basePath; if (string.IsNullOrEmpty(dirAbs)) { dirAbs = App.WebHostEnvironment.WebRootPath; } var fileFullPath = Path.Combine(dirAbs, fileInfo.FILE_PATH); if (!File.Exists(fileFullPath)) { throw Oops.Oh($"任务主键{taskPKId} 附件下载请求失败,请确认文件是否存在"); } _logger.LogInformation($"taskPKId={taskPKId} 下载文件完整路径 fileFullPath={fileFullPath}"); var fileName = HttpUtility.UrlEncode(fileInfo.FILE_NAME, Encoding.GetEncoding("UTF-8")); var result = new FileStreamResult(new FileStream(fileFullPath, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName }; return result; } #endregion #region 发送邮件 /// /// 发送邮件 /// /// DRAFT任务主键 /// 是否使用个人邮箱发送 /// 返回回执 [HttpGet("/TaskManageDRAFT/SendEmail")] public async Task SendEmail(string taskPKId, bool usePersonalEmailSend = false) { if (string.IsNullOrWhiteSpace(taskPKId)) throw Oops.Oh($"DRAFT任务主键不能为空"); var bcTaskInfo = await _taskBaseRepository.AsQueryable().FirstAsync(u => u.PK_ID == taskPKId); if (bcTaskInfo == null) { throw Oops.Oh($"任务主键{taskPKId}无法获取业务信息"); } var draft = _taskDraftInfoRepository.AsQueryable().First(a => a.TASK_ID == bcTaskInfo.PK_ID); if (draft == null) throw Oops.Oh($"任务主键{taskPKId}无法获取DRAFT业务信息"); return await GenerateSendEmail(draft, usePersonalEmailSend); } #endregion #region 生成并推送邮件 /// /// 生成并推送邮件 /// /// DRAFT任务详情 /// 返回回执 private async Task GenerateSendEmail(TaskDraftInfo taskDraftInfo, bool usePersonalEmailSend = false) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); try { /* 1、提取邮件接收人、通过订舱的委托客户获取联系人信息(提取联系人中备注是BCNotice的邮箱) 2、提取当票订舱对应的操作人邮箱、通过订舱的委托客户获取操作OP的邮箱 3、读取用户邮箱配置,主要提取显示名称BCNotice的邮箱,用来作为公共邮箱来发送邮件。 4、读取邮件模板,填充详情。 5、推送邮件给邮件接收人 */ //读取订舱数据 var bookingOrderEntity = _bookingOrderRepository.AsQueryable() .First(a => a.Id == taskDraftInfo.BOOKING_ID); if (bookingOrderEntity == null) { var checkInfo = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.MBLNO == taskDraftInfo.MBL_NO && a.IsDeleted == false && (a.ParentId == null || a.ParentId == 0)); if (checkInfo != null) { throw Oops.Oh($"订舱详情获取失败,用提单号能检索到,但是没有ID关系,需要人工看看问题"); } else { throw Oops.Oh($"订舱详情获取失败,请确认订舱是否存在或已删除"); } } _logger.LogInformation($"获取订舱详情完成,bookid={bookingOrderEntity.Id}"); if (!bookingOrderEntity.CUSTOMERID.HasValue || (bookingOrderEntity.CUSTOMERID.HasValue && bookingOrderEntity.CUSTOMERID.Value == 0)) { throw Oops.Oh($"订舱的委托客户不能为空"); } var djyCustomerInfo = _djyCustomerService.Detail(new GetDjyCustomerInput { Id = bookingOrderEntity.CUSTOMERID.Value }) .GetAwaiter().GetResult(); if (djyCustomerInfo == null) { throw Oops.Oh($"委托单位详情获取失败,请确认委托单位是否存在或已删除"); } _logger.LogInformation($"获取委托单位详情完成,djyCustomerId={djyCustomerInfo.Id}"); //DjyCustomerContactOutput djyCustomerContactMan = null; //TO 邮件接收人 string toEmail = string.Empty; //订舱OP的邮箱 string opEmail = string.Empty; var bookingContactList = _bookingOrderContactRepository.AsQueryable() .Where(a => a.BookingId == taskDraftInfo.BOOKING_ID).ToList(); if (bookingContactList == null || bookingContactList.Count == 0) { _logger.LogInformation($"当前订舱未指定的联系人,toEmail={toEmail}"); } toEmail = string.Join(";", bookingContactList.Select(x => x.Email.Trim()).Distinct().ToArray()); //获取操作OP的邮箱 if (!string.IsNullOrWhiteSpace(bookingOrderEntity.OPID)) { var opId = long.Parse(bookingOrderEntity.OPID); var opUser = _sysUserRepository.AsQueryable().First(a => a.Id == opId); if (opUser != null && !string.IsNullOrWhiteSpace(opUser.Email)) { opEmail = opUser.Email.Trim(); _logger.LogInformation($"获取操作OP的邮箱,opEmail={opEmail} id={opId} name={opUser.Name}"); } } //提取当前公共邮箱的配置 var publicMailAccount = _djyUserMailAccount.FirstOrDefault(x => x.CreatedUserId == UserManager.UserId && x.SmtpPort > 0 && x.SmtpServer != null && x.SmtpServer != ""); if (publicMailAccount == null) { throw Oops.Oh($"提取公共邮箱配置失败,请在用户邮箱账号管理增加配置显示名为BCNotice"); } _logger.LogInformation($"提取当前公共邮箱的配置完成,id={publicMailAccount.Id}"); string emailTitle = $"Draft : {taskDraftInfo.MBL_NO}"; string filePath = string.Empty; SysUser opUserInfo = null; if (!string.IsNullOrWhiteSpace(bookingOrderEntity.OPID) && Regex.IsMatch(bookingOrderEntity.OPID, "[0-9]+")) opUserInfo = _sysUserRepository.AsQueryable().First(u => u.Id == long.Parse(bookingOrderEntity.OPID)); //读取邮件模板并填充数据 string emailHtml = GenerateSendEmailHtml(taskDraftInfo, opUserInfo, UserManager.TENANT_NAME).GetAwaiter().GetResult(); _logger.LogInformation($"生成邮件BODY,结果:{emailHtml}"); var fileInfo = _taskFileRepository.AsQueryable().Where(a => a.TASK_PKID == taskDraftInfo.TASK_ID && a.FILE_CATEGORY.Contains("draft_notice")) .OrderByDescending(a => a.CreatedTime).First(); if (fileInfo == null) { throw Oops.Oh($"提取DRAFT的文件失败,不能发送邮件"); } _logger.LogInformation($"获取订舱附件地址,结果:{fileInfo.FILE_PATH}"); var opt = App.GetOptions(); var dirAbs = opt.basePath; if (string.IsNullOrEmpty(dirAbs)) { dirAbs = App.WebHostEnvironment.WebRootPath; } filePath = Path.Combine(dirAbs, fileInfo.FILE_PATH); EmailApiUserDefinedDto emailApiUserDefinedDto = new EmailApiUserDefinedDto { SendTo = toEmail, CCTo = opEmail, Title = emailTitle, Body = emailHtml, Account = publicMailAccount.MailAccount?.Trim(), Password = publicMailAccount.Password?.Trim(), Server = publicMailAccount.SmtpServer?.Trim(), Port = publicMailAccount.SmtpPort.HasValue ? publicMailAccount.SmtpPort.Value : 465, UseSSL = publicMailAccount.SmtpSSL.HasValue ? publicMailAccount.SmtpSSL.Value : true, Attaches = new List() }; _logger.LogInformation($"生成请求邮件参数,结果:{JSON.Serialize(emailApiUserDefinedDto)}"); //推送邮件 var emailRlt = await PushEmail(emailApiUserDefinedDto, filePath); _logger.LogInformation($"推送邮件完成,结果:{JSON.Serialize(emailRlt)}"); result.succ = true; result.msg = "成功"; } catch (Exception ex) { _logger.LogInformation($"推送邮件失败,异常:{ex.Message}"); result.succ = false; result.msg = $"推送邮件失败,{ex.Message}"; } return result; } #endregion #region 通过邮件模板生成HTML /// /// 通过邮件模板生成HTML /// /// BC任务详情 /// 订舱OP详情 /// 当前租户全称 /// 返回生成的HTML private async Task GenerateSendEmailHtml(TaskDraftInfo taskDraftInfo, SysUser opUserInfo, string tenantName) { string result = string.Empty; /* 1、加载模板文件,读取HTML 2、读取main、conta的tr行,替换业务数据 3、返回HTML的文本信息。 */ try { string templatePath = App.Configuration["EmailTemplateFilePath"]; var opt = App.GetOptions(); var dirAbs = opt.basePath; if (string.IsNullOrEmpty(dirAbs)) { dirAbs = App.WebHostEnvironment.WebRootPath; } templatePath = $"{dirAbs}{templatePath}\\DraftEmailTemplate.html"; string baseHtml = File.ReadAllText(templatePath); if (string.IsNullOrWhiteSpace(baseHtml)) throw Oops.Oh($"读取邮件模板失败"); if (opUserInfo != null && !string.IsNullOrWhiteSpace(opUserInfo.Name)) { baseHtml = baseHtml.Replace("#opname#", opUserInfo.Name); } else { baseHtml = baseHtml.Replace("#opname#", "操作"); } if (opUserInfo != null && !string.IsNullOrWhiteSpace(opUserInfo.Email)) { baseHtml = baseHtml.Replace("#opemail#", opUserInfo.Email); } else { baseHtml = baseHtml.Replace("#opemail#", ""); } if (opUserInfo != null && !string.IsNullOrWhiteSpace(opUserInfo.Phone)) { baseHtml = baseHtml.Replace("#optel#", opUserInfo.Phone); } else if (opUserInfo != null && !string.IsNullOrWhiteSpace(opUserInfo.Tel)) { baseHtml = baseHtml.Replace("#optel#", opUserInfo.Tel); } else { baseHtml = baseHtml.Replace("#optel#", ""); } HtmlDocument html = new HtmlDocument(); html.LoadHtml(baseHtml); HtmlNode baseTable = html.DocumentNode.SelectNodes("//table[@class='base-table']").FirstOrDefault(); if (baseTable == null) throw Oops.Oh($"读取邮件模板格式错误,定位base-table失败"); var baseTrList = baseTable.SelectNodes(".//tr"); foreach (var baseTr in baseTrList) { var tdList = baseTr.SelectNodes(".//td"); foreach (var baseTd in tdList) { if (baseTd.Attributes["class"].Value == "billno-val") { baseTd.InnerHtml = taskDraftInfo.MBL_NO; } else if (baseTd.Attributes["class"].Value == "carrier-val") { baseTd.InnerHtml = taskDraftInfo.CARRIER; } } } var noreplyTr = html.DocumentNode.SelectNodes("//tr[@class='email-noreply']").FirstOrDefault(); if (noreplyTr != null) { var currTd = noreplyTr.SelectNodes(".//td").FirstOrDefault(); if (currTd != null) { var currPList = currTd.SelectNodes(".//p"); foreach (var baseP in currPList) { if (baseP.Attributes["class"].Value == "notice-comp-val") { baseP.InnerHtml = tenantName; } } } } result = html.DocumentNode.OuterHtml; } catch (Exception ex) { _logger.LogInformation($"通过邮件模板生成HTML异常,原因={ex.Message}"); throw ex; } return result; } #endregion #region 推送邮件 /// /// 推送邮件 /// /// 自定义邮件详情 /// 文件路径 /// 返回回执 private async Task PushEmail(EmailApiUserDefinedDto emailApiUserDefinedDto, string filePath) { CommonWebApiResult result = new CommonWebApiResult { succ = true }; List emailList = new List(); var emailUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == "email_api_url")?.Value; if (emailUrl == null) throw Oops.Bah("字典未配置 url_set->email_api_url 请联系管理员"); System.IO.FileStream file = new System.IO.FileStream(filePath, FileMode.Open, FileAccess.Read); int SplitSize = 5242880;//5M分片长度 int index = 1; //序号 第几片 long StartPosition = 5242880 * (index - 1); long lastLens = file.Length - StartPosition;//真不知道怎么起命了,就这样吧 if (lastLens < 5242880) { SplitSize = (int)lastLens; } byte[] heByte = new byte[SplitSize]; file.Seek(StartPosition, SeekOrigin.Begin); //第一个参数是 起始位置 file.Read(heByte, 0, SplitSize); //第三个参数是 读取长度(剩余长度) file.Close(); string base64Str = Convert.ToBase64String(heByte); emailApiUserDefinedDto.Attaches.Add(new AttachesInfo { AttachName = Path.GetFileName(filePath), AttachContent = base64Str }); emailList.Add(emailApiUserDefinedDto); string strJoin = System.IO.File.ReadAllText(filePath); DateTime bDate = DateTime.Now; HttpResponseMessage res = null; try { res = await emailUrl.SetBody(emailList, "application/json").PostAsync(); } catch (Exception ex) { _logger.LogInformation($"发送邮件异常:{ex.Message}"); } DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation($"邮件上传完成 上传文件大小:{heByte.Length} 用时:{timeDiff}ms.,{strJoin}"); _logger.LogInformation($"发送邮件返回:{JSON.Serialize(res)}"); if (res != null && res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); var respObj = JsonConvert.DeserializeAnonymousType(userResult, new { Success = false, Message = string.Empty, Code = -9999, }); result.succ = respObj.Success; result.msg = respObj.Message; } return result; } #endregion #region 重新处理DRAFT任务 /// /// 重新处理DRAFT任务 /// 对未匹配订舱订单的任务记录重新对应订舱订单 /// /// DRAFT任务主键 /// [HttpGet("/TaskManageDRAFT/SearchAndConnectBookingInfo")] public async Task SearchAndConnectBookingInfo(string taskPkId) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); var taskBase = _taskBaseRepository.AsQueryable().First(a => a.PK_ID == taskPkId); if (taskBase == null) throw Oops.Oh($"任务主键{taskPkId}无法获取业务信息"); var draft = _taskDraftInfoRepository.AsQueryable().First(a => a.TASK_ID == taskBase.PK_ID); if (draft == null) throw Oops.Oh($"DRAFT主键{taskPkId}无法获取业务信息"); if (draft.BOOKING_ID.HasValue) throw Oops.Oh($"当前DRAFT已有匹配的订舱订单"); string mblNo = draft.MBL_NO.ToUpper().Trim(); var bookingInfo = _bookingOrderRepository.AsQueryable().Filter(null, true) .First(a => a.MBLNO == mblNo && a.IsDeleted == false && (a.ParentId == null || a.ParentId == 0)); if (bookingInfo == null) throw Oops.Oh($"提单号{mblNo}未提取有效的订舱订单"); draft.BOOKING_ID = bookingInfo.Id; draft.UpdatedUserId = UserManager.UserId; draft.UpdatedUserName = UserManager.Name; //更新任务 await _taskDraftInfoRepository.AsUpdateable(draft).UpdateColumns(it => new { it.BOOKING_ID, it.UpdatedTime, it.UpdatedUserId, it.UpdatedUserName }).ExecuteCommandAsync(); result.succ = true; result.msg = "成功"; return result; } #endregion } }