using Furion.FriendlyException; using Furion.JsonSerialization; using Furion; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Furion.RemoteRequest.Extensions; using Furion.DependencyInjection; using Microsoft.AspNetCore.Mvc; using System.Xml.Linq; using Furion.DynamicApiController; using Microsoft.AspNetCore.Authorization; using Myshipping.Application.Entity; using Myshipping.Core; using Furion.DistributedIDGenerator; using Mapster; using Furion.DataValidation; using Newtonsoft.Json.Linq; using Myshipping.Core.Service; using Myshipping.Core.Entity; using Microsoft.Extensions.Logging; namespace Myshipping.Application { /// /// 请求规则平台 /// [ApiDescriptionSettings("Application", Name = "RulesEngineClient", Order = 9)] public class RulesEngineClientService: IRulesEngineClientService, IDynamicApiController, ITransient { private readonly SqlSugarRepository _bookingOrderRepository; private readonly SqlSugarRepository _bookingOrderContaRepository; private readonly SqlSugarRepository _bookingOrderContaCargoRepository; private readonly ISysCacheService _cache; private readonly SqlSugarRepository _relaPortCarrierLaneRep; private readonly ILogger _logger; const string CONST_MAPPING_MODULE = "RULE_ENGINE_CHECK"; /// /// /// public RulesEngineClientService(SqlSugarRepository bookingOrderRepository, SqlSugarRepository bookingOrderContaRepository, SqlSugarRepository bookingOrderContaCargoRepository, ISysCacheService cache, SqlSugarRepository relaPortCarrierLaneRep, ILogger logger) { _bookingOrderRepository = bookingOrderRepository; _bookingOrderContaRepository = bookingOrderContaRepository; _bookingOrderContaCargoRepository = bookingOrderContaCargoRepository; _cache = cache; _relaPortCarrierLaneRep = relaPortCarrierLaneRep; _logger = logger; } #region 海运订舱请求规则引擎校验 /// /// 海运订舱请求规则引擎校验 /// /// 海运订舱请求业务 /// 返回用户信息 [HttpPost("/RulesEngineClient/ExcuteRulesOceanBookingByMsg")] public async Task ExcuteRulesOceanBookingByMsg(RulesEngineOrderBookingMessageInfo model) { RulesEngineExcuteResultDto result = new RulesEngineExcuteResultDto(); var ruleResult = await ExcuteRulesEngine(model); if (ruleResult == null) throw Oops.Oh($"订舱请求规则失败,返回为空"); var innerRlt = JSON.Deserialize(ruleResult.extra.ToString()); var ruleDetailList = innerRlt.DetailList; result.succ = ruleResult.succ; result.msg = ruleResult.msg; if (ruleDetailList.Count > 0) { result.rows = ruleDetailList; } return result; } #endregion #region 海运订舱请求规则引擎校验 /// /// 海运订舱请求规则引擎校验 /// /// 海运订舱主键 /// 返回用户信息 [HttpGet("/RulesEngineClient/ExcuteRulesOceanBooking")] public async Task ExcuteRulesOceanBooking(string bookingId) { string batchNo = IDGen.NextID().ToString(); _logger.LogInformation("批次={no}获取订舱数据请求规则 {id}", batchNo, bookingId); /* 处理逻辑 1、订单保存后触发调取此方法,传入海运订舱主键。 2、调取订舱的详情。 3、对应请求报文。 4、请求规则接口。 5、返回回执。 */ RulesEngineExcuteResultDto result = new RulesEngineExcuteResultDto(); try { DateTime nowDate = DateTime.Now; var model = _bookingOrderRepository.AsQueryable().InSingle(long.Parse(bookingId)); if (model == null) throw Oops.Oh($"订舱主键{bookingId}无法获取业务信息"); _logger.LogInformation("批次={no}获取订舱数据完成", batchNo); var mainInfo = model.Adapt(); var contaList = await _bookingOrderContaRepository.AsQueryable().Where(x => x.BILLID == model.Id).ToListAsync(); _logger.LogInformation("批次={no} 提取箱完成 数量={total}", batchNo, contaList.Count); if (contaList.Count > 0) { mainInfo.ContaList = contaList.Adapt>(); var ctnArg = contaList.Select(t => t.Id).ToArray(); var cargoList = await _bookingOrderContaCargoRepository.AsQueryable() .Where(x => ctnArg.Contains(x.CTNID.Value)).ToListAsync(); _logger.LogInformation("批次={no} 提取货物明细完成 数量={total}", batchNo, cargoList.Count); if (cargoList.Count > 0) { mainInfo.ContaList = contaList.GroupJoin(cargoList, l => l.Id, r => r.CTNID, (l, r) => { var currList = r.ToList(); if (currList.Count > 0) { var info = l.Adapt(); info.CargoList = currList.Adapt>(); return info; } return l.Adapt(); }).ToList(); } else { mainInfo.ContaList = contaList.Adapt>(); } } var msgModel = GetMessageInfo(batchNo, mainInfo); _logger.LogInformation("批次={no} 对应请求报文完成 msg={msg}", batchNo, JSON.Serialize(msgModel)); DateTime bDate = DateTime.Now; var ruleResult = await ExcuteRulesEngine(msgModel); DateTime eDate = DateTime.Now; TimeSpan ts = eDate.Subtract(bDate); var timeDiff = ts.TotalMilliseconds; _logger.LogInformation("批次={no} 请求完成,耗时:{timeDiff}ms. 结果{msg}", batchNo, timeDiff, ruleResult.succ ? "成功" : "失败"); if (ruleResult == null) throw Oops.Oh($"订舱主键{bookingId}请求规则失败,返回为空"); var innerRlt = JSON.Deserialize(ruleResult.extra.ToString()); var ruleDetailList = innerRlt.DetailList; result.succ = ruleResult.succ; result.msg = ruleResult.msg; if (ruleDetailList != null && ruleDetailList.Count > 0) { result.rows = ruleDetailList.OrderBy(t=>t.ErrorType).ToList(); } _logger.LogInformation("批次={no} 返回结果{msg}", batchNo,JSON.Serialize(result)); } catch(Exception ex) { result.succ = false; result.msg = $"请求规则异常,{ex.Message}"; } return result; } #endregion #region 海运订舱请求规则引擎校验 /// /// 海运订舱请求规则引擎校验 /// /// 海运订舱报文类 /// 返回回执 [HttpPost("/RulesEngineClient/ExcuteRulesOceanBookingByModel")] public async Task ExcuteRulesOceanBookingByModel(BookingOrderDto model) { string batchNo = IDGen.NextID().ToString(); /* 处理逻辑 1、前台将保存报文推送过来。 2、如果有订舱主键调取数据库内的订舱的详情,已推送的数据为准。 3、对应请求报文。 4、请求规则接口。 5、返回回执。 */ RulesEngineExcuteResultDto result = new RulesEngineExcuteResultDto(); try { DateTime nowDate = DateTime.Now; var mainInfo = model.Adapt(); RulesEngineOrderBookingMessageInfo msgModel = GetMessageInfo(batchNo, mainInfo); var ruleResult = await ExcuteRulesEngine(msgModel); if (ruleResult == null) throw Oops.Oh($"订舱主键{model.BOOKINGNO}请求规则失败,返回为空"); var innerRlt = JSON.Deserialize(ruleResult.extra.ToString()); var ruleDetailList = innerRlt.DetailList; result.succ = ruleResult.succ; result.msg = ruleResult.msg; if (ruleDetailList.Count > 0) { result.rows = ruleDetailList; } } catch (Exception ex) { result.succ = false; result.msg = "请求规则异常"; } return result; } #endregion /// /// 生成请求规则报文 /// /// 批次号 /// 订舱主业务信息 /// 返回请求报文类 [NonAction] private RulesEngineOrderBookingMessageInfo GetMessageInfo(string batchNo,RulesEngineOrderBookingMainBusinessInfo mainInfo) { DateTime nowDate = DateTime.Now; RulesEngineOrderBookingMessageInfo msgModel = new RulesEngineOrderBookingMessageInfo(); msgModel.Head = new RulesEngineWebAPIHeadBase { GID = batchNo, MessageType = "BUSI_RULE", SenderId = App.Configuration["RulesEngineSender"], SenderName = App.Configuration["RulesEngineSenderName"], SenderKey = App.Configuration["RulesEngineAuthKey"], ReceiverId = "RulesEngine", ReceiverName = "大简云规则引擎", Version = "1.0", RequestDate = nowDate.ToString("yyyy-MM-dd HH:mm:ss"), RequestAction = "CheckRule", }; msgModel.Main = new RulesEngineOrderBookingMainInfo { ProjectCode = App.Configuration["RulesEngineProjects"].Split(new char[] { ',' }).ToArray(), }; msgModel.Main.BusinessInfo = mainInfo; List codePortList = new List(); //根据卸货港翻译航线信息 if (!string.IsNullOrWhiteSpace(mainInfo.PortDischargeId)) { var laneList = _cache.GetAllRelaPortCarrierLane().GetAwaiter().GetResult(); var laneInfo = laneList .FirstOrDefault(p => !string.IsNullOrWhiteSpace(mainInfo.CarrierId) && !string.IsNullOrWhiteSpace(p.CarrierCode) && !string.IsNullOrWhiteSpace(p.PortCode) && p.CarrierCode.Equals(mainInfo.CarrierId, StringComparison.OrdinalIgnoreCase) && p.PortCode.Equals(mainInfo.PortDischargeId, StringComparison.OrdinalIgnoreCase)); if (laneInfo == null) laneInfo = laneList.FirstOrDefault(p => string.IsNullOrWhiteSpace(p.CarrierCode) && !string.IsNullOrWhiteSpace(p.PortCode) && p.PortCode.Equals(mainInfo.PortDischargeId, StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("批次={no} 港口对应航线完成 port={port} msg={msg}", batchNo, mainInfo.PortDischargeId, JSON.Serialize(laneInfo)); if (laneInfo != null) { var lineModel = _cache.GetAllCodeLane().GetAwaiter().GetResult().FirstOrDefault(t => !string.IsNullOrWhiteSpace(t.Code) && t.Code.Equals(laneInfo.LaneCode, StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("批次={no} 检索航线完成 lane={lane} msg={msg}", batchNo, laneInfo.LaneCode, JSON.Serialize(lineModel)); msgModel.Main.BusinessInfo.LaneCode = laneInfo.LaneCode; msgModel.Main.BusinessInfo.LaneName = lineModel?.CnName; } codePortList = _cache.GetAllCodePort().GetAwaiter().GetResult(); //翻译卸货港对应的国家 var portInfo = codePortList.FirstOrDefault(t => !string.IsNullOrWhiteSpace(t.Code) && t.EdiCode.Equals(mainInfo.PortDischargeId, StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("批次={no} 检索港口完成 lane={lane} msg={msg}", batchNo, mainInfo.PortDischargeId, JSON.Serialize(portInfo)); if (portInfo == null || string.IsNullOrWhiteSpace(portInfo.CountryCode)) throw Oops.Oh($"卸货港口代码{mainInfo.PortDischargeId}获取港口基础数据失败"); var countryInfo = _cache.GetAllCodeCountry().GetAwaiter().GetResult().FirstOrDefault(t => !string.IsNullOrWhiteSpace(t.Code) && t.Code.Equals(portInfo.CountryCode, StringComparison.OrdinalIgnoreCase)); if (countryInfo == null || string.IsNullOrWhiteSpace(portInfo.EnName)) throw Oops.Oh($"国家代码{portInfo.CountryCode}获取国家基础数据失败"); msgModel.Main.BusinessInfo.PortDischargeCountryNo = countryInfo.Code; msgModel.Main.BusinessInfo.PortDischargeEN = countryInfo.EnName; msgModel.Main.BusinessInfo.PortDischargeCN = countryInfo.CnName; } //中转港 if (!string.IsNullOrWhiteSpace(mainInfo.TransportId)) { if(codePortList.Count == 0) { codePortList = _cache.GetAllCodePort().GetAwaiter().GetResult(); } //翻译中转港对应的国家 var portInfo = codePortList.FirstOrDefault(t => !string.IsNullOrWhiteSpace(t.Code) && t.EdiCode.Equals(mainInfo.TransportId, StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("批次={no} 检索港口完成 port={lane} msg={msg}", batchNo, mainInfo.TransportId, JSON.Serialize(portInfo)); if (portInfo == null || string.IsNullOrWhiteSpace(portInfo.CountryCode)) throw Oops.Oh($"中转港口代码{mainInfo.TransportId}获取港口基础数据失败"); var countryInfo = _cache.GetAllCodeCountry().GetAwaiter().GetResult().FirstOrDefault(t => !string.IsNullOrWhiteSpace(t.Code) && t.Code.Equals(portInfo.CountryCode, StringComparison.OrdinalIgnoreCase)); if (countryInfo == null || string.IsNullOrWhiteSpace(portInfo.EnName)) throw Oops.Oh($"国家代码{portInfo.CountryCode}获取国家基础数据失败"); msgModel.Main.BusinessInfo.TransportCountryNo = countryInfo.Code; msgModel.Main.BusinessInfo.TransportEN = countryInfo.EnName; msgModel.Main.BusinessInfo.TransportCN = countryInfo.CnName; } //对应签单方式 if (!string.IsNullOrWhiteSpace(mainInfo.IssueType)) { var issueType = _cache.GetAllCodeIssueType().GetAwaiter().GetResult().FirstOrDefault(p => !string.IsNullOrWhiteSpace(p.EnName) && p.EnName.Equals(mainInfo.IssueType, StringComparison.OrdinalIgnoreCase)); _logger.LogInformation("批次={no} 提取签单方式完成 IssueType={issue} msg={msg}", batchNo, mainInfo.IssueType, JSON.Serialize(issueType)); //这里规则约定用了签单方式的代码大写 if (issueType != null) msgModel.Main.BusinessInfo.IssueType = issueType.Code.ToUpper(); } if (mainInfo.ContaList != null && mainInfo.ContaList.Count > 0) { //var contaList = _cache.GetAllMappingCtn().GetAwaiter().GetResult(); //mainInfo.ContaList.ForEach(t => //{ // var currContaType = contaList.FirstOrDefault(x => x.Module.Equals(CONST_MAPPING_MODULE, StringComparison.OrdinalIgnoreCase) // && x.Code.Equals(t.ContaType, StringComparison.OrdinalIgnoreCase)); // if (currContaType != null) // { // t.ContaType = currContaType.MapCode?.ToUpper(); // } //}); mainInfo.ContaList.ForEach(t => { if (!string.IsNullOrWhiteSpace(t.ContaType)) { var contaInfo = _cache.GetAllCodeCtn().GetAwaiter().GetResult().FirstOrDefault(p => !string.IsNullOrWhiteSpace(p.Code) && p.Code.Equals(t.ContaType, StringComparison.OrdinalIgnoreCase)); if (contaInfo == null) throw Oops.Oh($"箱型{t.ContaType} 基础数据没有指定大小箱"); t.ContaCategory = contaInfo.CtnCategory; } }); } return msgModel; } #region 请求规则平台 /// /// 请求规则平台 /// /// /// [NonAction] private async Task ExcuteRulesEngine(RulesEngineOrderBookingMessageInfo info) { RulesEngineWebApiResult model = null; /* 1、读取配置文件中的规则引擎URL 2、填充请求的类,并生成JSON报文 3、POST请求接口,并记录回执。 4、返回信息。 */ var url = App.Configuration["RulesEngineUrl"]; try { var res = await url.SetHttpMethod(HttpMethod.Post) .SetBody(JSON.Serialize(info), "application/json") .SetContentEncoding(Encoding.UTF8) .PostAsync(); _logger.LogInformation("批次={no} 对应请求报文完成 res={res}", info.Head.GID, 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 } }