using Furion; using Furion.DependencyInjection; using Furion.DistributedIDGenerator; using Furion.DynamicApiController; using Furion.FriendlyException; using Furion.JsonSerialization; using Furion.RemoteRequest.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Myshipping.Application.ConfigOption; using Myshipping.Application.Entity; using Myshipping.Core; using Myshipping.Core.Service; using Org.BouncyCastle.Crypto; using StackExchange.Profiling.Internal; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Net.NetworkInformation; using System.Runtime.InteropServices; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; using Yitter.IdGenerator; namespace Myshipping.Application { /// /// 订舱增值类服务 /// [ApiDescriptionSettings("Application", Name = "BookingValueAdded", Order = 9)] public class BookingValueAddedService : IBookingValueAddedService, IDynamicApiController, ITransient { private readonly ISysCacheService _cache; private readonly IDjyWebsiteAccountConfigService _webAccountConfig; private readonly ILogger _logger; private readonly SqlSugarRepository _bookingOrderRepository; private readonly SqlSugarRepository _bookingfile; const string CONST_MAPPING_BC_MODULE_ROUTE = "BC_DOWN_RT"; const string CONST_MAPPING_DRAFT_MODULE_ROUTE = "DRAFT_DOWN_RT"; const string CONST_MAPPING_MANIALLO_CHK_MODULE_ROUTE = "MANI_ALLOC_CHK_RT"; const string CONST_FORMAT_BC_URL = "{0}_bc_down_url"; const string CONST_FORMAT_DRAFT_URL = "{0}_draft_down_url"; const string CONST_FORMAT_MANIALLO_CHK_URL = "{0}_manialloc_chk_url"; const string CONST_FORMAT_WEB = "{0}Web"; public BookingValueAddedService(ISysCacheService cache, ILogger logger, SqlSugarRepository bookingOrderRepository, SqlSugarRepository bookingfile, IDjyWebsiteAccountConfigService webAccountConfig) { _cache = cache; _logger = logger; _bookingOrderRepository = bookingOrderRepository; _bookingfile = bookingfile; _webAccountConfig = webAccountConfig; } /// /// 批量BC下载 /// /// 订舱主键数组 /// 返回回执 [HttpPost("/BookingValueAdded/DownloadBookingConfirm")] public async Task DownloadBookingConfirm([FromBody]long[] bookingIds) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); List> taskList = new List>(); string batchNo = IDGen.NextID().ToString(); try { /* 1、订舱主键提取提单号、订舱编号,优先去提单号,其次订舱编号 2、根据不同的船公司调取单独的配置账户。 3、不同船公司对应不同的接口。 4、轮询异步调取接口等待返回接口。 5、成功后将文件链接存入附件表。 6、等待所有请求完成返回统计结果。 */ var list = _bookingOrderRepository.AsQueryable() .Where(a => bookingIds.Contains(a.Id)).ToList(); if (list.Count != bookingIds.Length) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); var noList = bookingIds.Select((a, idx) => new { no = idx + 1, id = a }).ToList(); foreach (var bk in list) { var sortNo = noList.FirstOrDefault(a=>a.id == bk.Id).no; taskList.Add(Task.Run(() => InnerDownloadBookingConfirm(bk, batchNo, sortNo))); } Task.WaitAll(taskList.ToArray()); result.succ = true; result.msg = "批量下载BC成功"; var downResultList = taskList.Select(x => x.Result).ToList(); if (downResultList.Any(x => !x.succ)) { result.succ = false; result.msg = "BC下载失败"; } else { result.succ = true; result.msg = downResultList.FirstOrDefault().msg; } result.ext = downResultList; var succ = downResultList.Count(x => x.succ); var fail = downResultList.Count(x => !x.succ); if (succ > 0) result.batchTotal = succ.ToString(); if(fail > 0) { if (succ > 0) result.batchTotal = result.batchTotal+"/"+ fail.ToString(); result.batchTotal = "/" + fail.ToString(); } } catch (Exception ex) { result.succ = false; result.msg = $"批量BC下载异常,原因:{ex.Message}"; } return result; } #region 单票下载BC /// /// 单票下载BC /// /// 订舱详情 /// 批次号 /// 请求顺序号 /// 返回回执 private async Task InnerDownloadBookingConfirm(BookingOrder bookingOrder,string batchNo, int sortNo) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); result.bno = bookingOrder.MBLNO; try { /* 1、根据船公司代码匹配船公司映射。 2、BC和DRAFT是分别2个请求地址。 3、生成请求报文。 4、请求相应的链接。 5、返回成功写入附件。 */ if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"主提单号不能为空"); } var bcOrDraftRouteCfg = _cache.GetAllMappingCarrier().GetAwaiter().GetResult() .FirstOrDefault(t => t.Module.Equals(CONST_MAPPING_BC_MODULE_ROUTE, StringComparison.OrdinalIgnoreCase) && t.Code.Equals(bookingOrder.CARRIERID?.Trim(), StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("提单号【{mbl}】根据订舱的船公司代码{ca} 提取船公司映射完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcOrDraftRouteCfg); if (bcOrDraftRouteCfg == null) { _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取船公司映射失败", bookingOrder.MBLNO, bookingOrder.CARRIERID); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 暂不支持BC下载"); } string urlKey = string.Format(CONST_FORMAT_BC_URL, bcOrDraftRouteCfg.MapCode.ToLower()); var bcUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code.Equals(urlKey, StringComparison.OrdinalIgnoreCase))?.Value; _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取BC下载URL完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取BC下载URL失败,未取到配置key={key}", bookingOrder.MBLNO, bookingOrder.CARRIERID, urlKey); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 未配置请求地址{urlKey} 请联系管理员"); } string webKey = string.Format(CONST_FORMAT_WEB, bcOrDraftRouteCfg.MapCode); //获取个人对应的账户,这里GetAccountConfig逻辑优先取个人,个人没有配置取公司对应配置 var userWebAccountConfig = _webAccountConfig.GetAccountConfig(webKey, UserManager.UserId).GetAwaiter() .GetResult(); _logger.LogInformation("批次={no} 获取获取网站的账户完成,result={Num}", batchNo, JSON.Serialize(userWebAccountConfig)); if (userWebAccountConfig == null) throw Oops.Oh($" 未配置个人或公司网站账户,网站{webKey}"); BCOrDraftRequestDto requestDto = new BCOrDraftRequestDto { user_key = App.Configuration["BCOrDraftUserKey"], user_secret = App.Configuration["BCOrDraftUserSecret"], web_user = userWebAccountConfig.Account?.Trim(), web_psw = userWebAccountConfig.Password?.Trim(), bno = bookingOrder.MBLNO, is_parse = false }; _logger.LogInformation("批次={no} json={json} 请求BC远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteBCDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求BC远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt),timeDiff); if (rlt.code == 200) { _logger.LogInformation("批次={no} 下载文件成功,转存本地", batchNo); string currFilePath = rlt.data.FirstOrDefault().path; string fileTypeCode = "bc"; string fileTypeName = "Booking Confirmation"; //读取文件配置 var fileCfg = App.GetOptions(); string relativePath = $"{fileCfg.relativePath}\\bcfiles\\{bookingOrder.Id}"; string filePath = $"{(!string.IsNullOrWhiteSpace(fileCfg.basePath) ? fileCfg.basePath : App.WebHostEnvironment.WebRootPath)}\\{relativePath}"; string fileFullName = $"{filePath}\\{new System.IO.FileInfo(currFilePath).Name}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { relativePath = relativePath.Replace("\\", "/"); filePath = filePath.Replace("\\", "/"); fileFullName = fileFullName.Replace("\\", "/"); } _logger.LogInformation("批次={no} 生成文件保存路径完成 路由={filePath} 服务器系统={system}", batchNo, filePath, RuntimeInformation.OSDescription); //预先创建目录 if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } var bcStream = await currFilePath.GetAsStreamAsync(); using (var fileStream = File.Create(fileFullName)) { await bcStream.CopyToAsync(fileStream); } _logger.LogInformation("批次={no} 完成文件保存 filepath={path}", batchNo, fileFullName); string bookFilePath = string.Empty; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("/", "\\/") + ".*").Value; } else { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("\\", "\\\\") + ".*").Value; } //这里先写入附件表 await SaveEDIFile(bookingOrder.Id, bookFilePath, new System.IO.FileInfo(currFilePath).Name, fileTypeCode, fileTypeName); result.succ = true; result.msg = "BC下载成功"; } else { result.succ = false; result.msg = $"BC下载失败,{rlt.msg}"; } } catch (Exception ex) { result.succ = false; result.msg = $"BC下载失败,{ex.Message}"; } return result; } #endregion #region 异步写入订舱附件表 /// /// 异步写入订舱附件表 /// /// 订舱ID /// 文件路径 /// 文件名 /// 附件类型代码 /// 附件类型名称 /// [NonAction] private async Task SaveEDIFile(long boookId, string FilePath, string fileName, string fileTypeCode = "bc", string fileTypeName = "Booking Confirmation") { /* 直接将附件信息写入附件表 */ //EDI文件 var bookFile = new BookingFile { Id = YitIdHelper.NextId(), FileName = fileName, FilePath = FilePath, TypeCode = fileTypeCode, TypeName = fileTypeName, BookingId = boookId, }; await _bookingfile.InsertAsync(bookFile); } #endregion /// /// 批量Draft下载 /// /// 订舱主键数组 /// [HttpPost("/BookingValueAdded/DownloadDraft")] public async Task DownloadDraft([FromBody]long[] bookingIds) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); List> taskList = new List>(); string batchNo = IDGen.NextID().ToString(); try { /* 1、订舱主键提取提单号、订舱编号,优先去提单号,其次订舱编号 2、根据不同的船公司调取单独的配置账户。 3、不同船公司对应不同的接口。 4、轮询异步调取接口等待返回接口。 5、成功后将文件链接存入附件表。 6、等待所有请求完成返回统计结果。 */ var list = _bookingOrderRepository.AsQueryable() .Where(a => bookingIds.Contains(a.Id)).ToList(); if (list.Count != bookingIds.Length) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); var noList = bookingIds.Select((a, idx) => new { no = idx + 1, id = a }).ToList(); foreach (var bk in list) { var sortNo = noList.FirstOrDefault(a=>a.id == bk.Id).no; taskList.Add(Task.Run(() => InnerDownloadDraft(bk, batchNo, sortNo))); } Task.WaitAll(taskList.ToArray()); result.succ = true; result.msg = "下载Draft成功"; var downResultList = taskList.Select(x => x.Result).ToList(); if (downResultList.Any(x => !x.succ)) { result.succ = false; result.msg = "Draft下载失败"; } else { result.succ = true; result.msg = downResultList.FirstOrDefault().msg; } result.ext = downResultList; var succ = downResultList.Count(x => x.succ); var fail = downResultList.Count(x => !x.succ); if (succ > 0) result.batchTotal = succ.ToString(); if (fail > 0) { if (succ > 0) result.batchTotal = result.batchTotal + "/" + fail.ToString(); result.batchTotal = "/" + fail.ToString(); } } catch (Exception ex) { result.succ = false; result.msg = $"下载Draft异常,原因:{ex.Message}"; } return result; } #region 单票下载BC /// /// 单票下载BC /// /// 订舱详情 /// 批次号 /// 请求顺序号 /// 返回回执 private async Task InnerDownloadDraft(BookingOrder bookingOrder, string batchNo,int sortNo) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); result.bno = bookingOrder.MBLNO; try { /* 1、根据船公司代码匹配船公司映射。 2、BC和DRAFT是分别2个请求地址。 3、生成请求报文。 4、请求相应的链接。 5、返回成功写入附件。 */ if(bookingOrder.CARRIERID.Equals("ESL", StringComparison.OrdinalIgnoreCase)) { if(string.IsNullOrWhiteSpace(bookingOrder.TMBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"EP号不能为空"); } } else { if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"主提单号不能为空"); } } var bcOrDraftRouteCfg = _cache.GetAllMappingCarrier().GetAwaiter().GetResult() .FirstOrDefault(t => t.Module.Equals(CONST_MAPPING_DRAFT_MODULE_ROUTE, StringComparison.OrdinalIgnoreCase) && t.Code.Equals(bookingOrder.CARRIERID?.Trim(), StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取船公司映射完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcOrDraftRouteCfg); if (bcOrDraftRouteCfg == null) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取船公司映射失败", bookingOrder.MBLNO, bookingOrder.CARRIERID); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 暂不支持Draft下载"); } string urlKey = string.Format(CONST_FORMAT_DRAFT_URL, bcOrDraftRouteCfg.MapCode.ToLower()); var bcUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code.Equals(urlKey, StringComparison.OrdinalIgnoreCase))?.Value; _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取DRAFT下载URL完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取BC下载URL失败,未取到配置key={key}", bookingOrder.MBLNO, bookingOrder.CARRIERID, urlKey); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 未配置请求地址{urlKey} 请联系管理员"); } string webKey = string.Format(CONST_FORMAT_WEB, bcOrDraftRouteCfg.MapCode); //获取个人对应的账户,这里GetAccountConfig逻辑优先取个人,个人没有配置取公司对应配置 var userWebAccountConfig = _webAccountConfig.GetAccountConfig(webKey, UserManager.UserId).GetAwaiter() .GetResult(); _logger.LogInformation("批次={no} 获取获取网站的账户完成,result={Num}", batchNo, JSON.Serialize(userWebAccountConfig)); if (userWebAccountConfig == null) throw Oops.Oh($" 未配置个人或公司网站账户,网站{webKey}"); string downloadFilePathRlt = string.Empty; string erroMsg = string.Empty; if(bcOrDraftRouteCfg.MapCode.Trim().Equals("ESL", StringComparison.OrdinalIgnoreCase)) { ESLDraftRequestDto requestDto = new ESLDraftRequestDto { u = userWebAccountConfig.Account?.Trim(), p = userWebAccountConfig.Password?.Trim(), ep_code = bookingOrder.TMBLNO?.Trim().ToUpper(), }; _logger.LogInformation("批次={no} json={json} 请求Draft远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteESLDraftDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求Draft远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt), timeDiff); if (rlt.status == 1) { downloadFilePathRlt = rlt.data.api_path; } else { erroMsg = rlt.message; } } else { BCOrDraftRequestDto requestDto = new BCOrDraftRequestDto { user_key = App.Configuration["BCOrDraftUserKey"], user_secret = App.Configuration["BCOrDraftUserSecret"], web_user = userWebAccountConfig.Account?.Trim(), web_psw = userWebAccountConfig.Password?.Trim(), bno = bookingOrder.MBLNO, is_parse = false }; _logger.LogInformation("批次={no} json={json} 请求Draft远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteDraftDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求Draft远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt), timeDiff); if (rlt.code == 200) { downloadFilePathRlt = rlt.data.FirstOrDefault().path; } else { erroMsg = rlt.msg; } } if (!string.IsNullOrWhiteSpace(downloadFilePathRlt)) { _logger.LogInformation("批次={no} 下载文件成功,转存本地", batchNo); string currFilePath = downloadFilePathRlt; string fileTypeCode = "draft"; string fileTypeName = "Draft"; //读取文件配置 var fileCfg = App.GetOptions(); string relativePath = $"{fileCfg.relativePath}\\draftfiles\\{bookingOrder.Id}"; string filePath = $"{(!string.IsNullOrWhiteSpace(fileCfg.basePath) ? fileCfg.basePath : App.WebHostEnvironment.WebRootPath)}\\{relativePath}"; string fileFullName = $"{filePath}\\{new System.IO.FileInfo(currFilePath).Name}"; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { relativePath = relativePath.Replace("\\", "/"); filePath = filePath.Replace("\\", "/"); fileFullName = fileFullName.Replace("\\", "/"); } _logger.LogInformation("批次={no} 生成文件保存路径完成 路由={filePath} 服务器系统={system}", batchNo, filePath, RuntimeInformation.OSDescription); //预先创建目录 if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); } var bcStream = await currFilePath.GetAsStreamAsync(); using (var fileStream = File.Create(fileFullName)) { await bcStream.CopyToAsync(fileStream); } _logger.LogInformation("批次={no} 完成文件保存 filepath={path}", batchNo, fileFullName); string bookFilePath = string.Empty; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("/", "\\/") + ".*").Value; } else { bookFilePath = System.Text.RegularExpressions.Regex.Match(fileFullName, relativePath.Replace("\\", "\\\\") + ".*").Value; } //这里先写入附件表 await SaveEDIFile(bookingOrder.Id, bookFilePath, new System.IO.FileInfo(currFilePath).Name, fileTypeCode, fileTypeName); result.succ = true; result.msg = "Draft下载成功"; } else { result.succ = false; result.msg = $"Draft下载失败,原因={erroMsg}"; } } catch (Exception ex) { result.succ = false; result.msg = $"Draft下载失败,原因:{ex.Message}"; } return result; } #endregion #region BC请求远端下载 /// /// BC请求远端下载 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteBCDownload(string url, BCOrDraftRequestDto info, string batchNo) { BCAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); System.Text.Json.JsonSerializerOptions jsonOptions = new JsonSerializerOptions(); jsonOptions.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); model = JSON.Deserialize(userResult, jsonOptions); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion #region Draft请求远端下载 /// /// Draft请求远端下载 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteDraftDownload(string url, BCOrDraftRequestDto info, string batchNo) { DraftAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); System.Text.Json.JsonSerializerOptions jsonOptions = new JsonSerializerOptions(); jsonOptions.Converters.Add(new DateTimeJsonConverter("yyyy-MM-dd HH:mm:ss")); model = JSON.Deserialize(userResult, jsonOptions); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion #region ESL Draft请求远端下载 /// /// ESL Draft请求远端下载 /// 由于现有的ESL和TSL接口不一致,需要提供单独的POST方法 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteESLDraftDownload(string url, ESLDraftRequestDto info, string batchNo) { ESLDraftAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.OnClientCreating(client => { // client 为 HttpClient 对象 client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 }).SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); model = JSON.Deserialize(userResult); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion #region ESL SO Ref.No搜索ESL号查询 /// /// ESL SO Ref.No搜索ESL号查询 /// /// 请求URL /// 请求详情 /// 批次号 /// 返回结果 [NonAction] private async Task ExcuteManiAllocCheckDownload(string url, ESLManiAlloChkRequestDto info, string batchNo) { ESLManiAlloChkAPIBaseResult model = null; /* 1、填充请求的类,并生成JSON报文 2、POST请求接口,并记录回执。 3、返回信息。 */ try { _logger.LogInformation("批次={no} 对应请求报文 request={res}", batchNo, JSON.Serialize(info)); var res = await url.OnClientCreating(client => { // client 为 HttpClient 对象 client.Timeout = TimeSpan.FromMinutes(15); // 设置超时时间 15分钟 }).SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", batchNo, JSON.Serialize(res)); if (res.StatusCode == System.Net.HttpStatusCode.OK) { var userResult = await res.Content.ReadAsStringAsync(); model = JSON.Deserialize(userResult); } } catch (Exception ex) { //写日志 if (ex is HttpRequestException) throw Oops.Oh(10000002); } return model; } #endregion /// /// 舱位分配查询 /// /// 订舱主键数组 /// 返回回执 [HttpPost("/BookingValueAdded/CheckUpdateManifestNo")] public async Task CheckUpdateManifestNo([FromBody] long[] bookingIds) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); List> taskList = new List>(); string batchNo = IDGen.NextID().ToString(); try { /* 现在只有ESL支持舱位分配查询 1、订舱主键提取提单号,判断提单号如果不是ESL开头的,需要启动流程 2、异步调取ESL接口。 3、返回结果后提取esl_no号 4、将参考号写入订舱编号。 5、将esl_no号写入主提单号。 6、等待所有请求完成返回统计结果。 */ var list = _bookingOrderRepository.AsQueryable() .Where(a => bookingIds.Contains(a.Id)).ToList(); if (list.Count != bookingIds.Length) throw Oops.Oh($"订舱信息获取失败,订舱信息不存在或已作废"); var noList = bookingIds.Select((a, idx) => new { no = idx + 1, id = a }).ToList(); foreach (var bk in list) { var sortNo = noList.FirstOrDefault(a => a.id == bk.Id).no; taskList.Add(Task.Run(() => InnerCheckUpdateManifestNo(bk, batchNo, sortNo))); } Task.WaitAll(taskList.ToArray()); result.succ = true; result.msg = "批量执行成功"; var downResultList = taskList.Select(x => x.Result).ToList(); if (downResultList.Any(x => !x.succ)) { result.succ = false; result.msg = "批量执行失败"; } else { result.succ = true; result.msg = downResultList.FirstOrDefault().msg; } result.ext = downResultList; var succ = downResultList.Count(x => x.succ); var fail = downResultList.Count(x => !x.succ); if (succ > 0) result.batchTotal = succ.ToString(); if (fail > 0) { if (succ > 0) result.batchTotal = result.batchTotal + "/" + fail.ToString(); result.batchTotal = "/" + fail.ToString(); } } catch (Exception ex) { result.succ = false; result.msg = $"批量执行失败,原因:{ex.Message}"; } return result; } #region 单票舱位分配查询 /// /// 单票舱位分配查询 /// /// 订舱详情 /// 批次号 /// 请求顺序号 /// 返回回执 private async Task InnerCheckUpdateManifestNo(BookingOrder bookingOrder, string batchNo, int sortNo) { TaskManageOrderResultDto result = new TaskManageOrderResultDto(); result.bno = bookingOrder.MBLNO; try { /* 1、根据船公司代码匹配船公司映射。 2、BC和DRAFT是分别2个请求地址。 3、生成请求报文。 4、请求相应的链接。 5、返回成功写入附件。 */ if (string.IsNullOrWhiteSpace(bookingOrder.MBLNO)) { if (!string.IsNullOrWhiteSpace(bookingOrder.CUSTNO)) { result.bno = $"订 {bookingOrder.CUSTNO}"; } else { result.bno = $"NO.{sortNo}"; } throw Oops.Bah($"主提单号不能为空"); } else { if (Regex.IsMatch(bookingOrder.MBLNO, "\\bESL\\w+")) { _logger.LogInformation("批次={no} id={id} 主提单号没填写参考号,无法继续", batchNo, bookingOrder.Id); throw Oops.Oh($"主提单号不是有效的参考号"); } } var bcOrDraftRouteCfg = _cache.GetAllMappingCarrier().GetAwaiter().GetResult() .FirstOrDefault(t => t.Module.Equals(CONST_MAPPING_MANIALLO_CHK_MODULE_ROUTE, StringComparison.OrdinalIgnoreCase) && t.Code.Equals(bookingOrder.CARRIERID?.Trim(), StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("提单号【{mbl}】根据订舱的船公司代码{ca} 提取船公司映射完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcOrDraftRouteCfg); if (bcOrDraftRouteCfg == null) { _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取船公司映射失败", bookingOrder.MBLNO, bookingOrder.CARRIERID); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 暂不支持舱位分配查询"); } string urlKey = string.Format(CONST_FORMAT_MANIALLO_CHK_URL, bcOrDraftRouteCfg.MapCode.ToLower()); var bcUrl = _cache.GetAllDictData().GetAwaiter().GetResult() .FirstOrDefault(x => x.TypeCode == "url_set" && x.Code.Equals(urlKey, StringComparison.OrdinalIgnoreCase))?.Value; _logger.LogInformation("提单号{mbl} 根据订舱的船公司代码{ca} 提取舱位分配查询URL完成,结果={rlt}", bookingOrder.MBLNO, bookingOrder.CARRIERID, bcUrl); if (string.IsNullOrWhiteSpace(bcUrl)) { _logger.LogInformation("提单号{0} 根据订舱的船公司代码{1} 提取舱位分配查询URL失败,未取到配置key={key}", bookingOrder.MBLNO, bookingOrder.CARRIERID, urlKey); throw Oops.Bah($"船公司={bookingOrder.CARRIERID} 未配置请求地址{urlKey} 请联系管理员"); } string webKey = string.Format(CONST_FORMAT_WEB, bcOrDraftRouteCfg.MapCode); //获取个人对应的账户,这里GetAccountConfig逻辑优先取个人,个人没有配置取公司对应配置 var userWebAccountConfig = _webAccountConfig.GetAccountConfig(webKey, UserManager.UserId).GetAwaiter() .GetResult(); _logger.LogInformation("批次={no} 获取获取网站的账户完成,result={Num}", batchNo, JSON.Serialize(userWebAccountConfig)); if (userWebAccountConfig == null) throw Oops.Oh($" 未配置个人或公司网站账户,网站{webKey}"); ESLManiAlloChkRequestDto requestDto = new ESLManiAlloChkRequestDto { u = userWebAccountConfig.Account?.Trim(), p = userWebAccountConfig.Password?.Trim(), so_no = bookingOrder.MBLNO, }; _logger.LogInformation("批次={no} json={json} 请求舱位分配查询远端下载开始", batchNo, JSON.Serialize(requestDto)); DateTime bDate = DateTime.Now; //开始请求BC var rlt = await ExcuteManiAllocCheckDownload(bcUrl, requestDto, batchNo); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} result={result} 请求舱位分配查询远端下载结束 耗时:{timeDiff}ms. ", batchNo, JSON.Serialize(rlt), timeDiff); if (rlt.status == 1) { if(rlt.data == null || string.IsNullOrWhiteSpace(rlt.data.esl_no)) { _logger.LogInformation("批次={no} 舱位分配查询失败,ESL号不存在", batchNo); throw Oops.Oh($"查询ESL号失败"); } _logger.LogInformation("批次={no} 舱位分配查询成功", batchNo); /* 提单号写入订舱编号,并把esl_no写入主提单号 */ var bkInfo = _bookingOrderRepository.AsQueryable().First(a => a.Id == bookingOrder.Id); if(bkInfo == null) { _logger.LogInformation("批次={no} id={id} 订舱信息获取失败,无法更新", batchNo, bookingOrder.Id); throw Oops.Oh($"订舱信息获取失败,无法更新"); } if(Regex.IsMatch(bkInfo.MBLNO,"\\bESL\\w+")) { _logger.LogInformation("批次={no} id={id} 主提单号已变更,无法更新", batchNo, bookingOrder.Id); throw Oops.Oh($"主提单号已变更,无法更新"); } _logger.LogInformation("批次={no} 变更前记录 id={id} MBLNO={MBLNO} CUSTNO={CUSTNO} esl_no={eslno}", batchNo, bookingOrder.Id, bkInfo.MBLNO, bkInfo.CUSTNO, rlt.data.esl_no); bkInfo.CUSTNO = bkInfo.MBLNO; bkInfo.MBLNO = rlt.data.esl_no.Trim().ToUpper(); bkInfo.UpdatedTime = DateTime.Now; bkInfo.UpdatedUserId = UserManager.UserId; bkInfo.UpdatedUserName = UserManager.Name; await _bookingOrderRepository.AsUpdateable(bkInfo).UpdateColumns(it => new { it.CUSTNO, it.MBLNO, it.UpdatedTime, it.UpdatedUserId, it.UpdatedUserName }).ExecuteCommandAsync(); result.succ = true; result.msg = "舱位分配查询成功"; } else { result.succ = false; result.msg = $"舱位分配查询失败,{rlt.message}"; } } catch (Exception ex) { result.succ = false; result.msg = $"舱位分配查询失败,{ex.Message}"; } return result; } #endregion } public class DateTimeJsonConverter : System.Text.Json.Serialization.JsonConverter { private readonly string _dateFormatString; public DateTimeJsonConverter() { _dateFormatString = "yyyy-MM-dd HH:mm:ss"; } public DateTimeJsonConverter(string dateFormatString) { _dateFormatString = dateFormatString; } public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return DateTime.Parse(reader.GetString()); } public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) { writer.WriteStringValue(value.ToUniversalTime().ToString(_dateFormatString)); } } }