using Furion.DependencyInjection; using Furion.FriendlyException; using Furion.RemoteRequest.Extensions; using Microsoft.Extensions.Logging; using Myshipping.Application.Entity; using Myshipping.Application.Service.ExpressDelivery.Dto; using Myshipping.Core; using Myshipping.Core.Service; using Newtonsoft.Json.Linq; using StackExchange.Profiling.Internal; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; namespace Myshipping.Application { /// /// 快递 /// public class SFDeliverySend : IDeliverySend, ITransient { /// /// Redis中用于保存访问顺丰API的Token的键名 /// private const string SF_API_TOKEN_KEY = $"SfApiToken"; private readonly ISysCacheService cache; private readonly ILogger logger; public SFDeliverySend(ISysCacheService cache, ILogger logger) { this.cache = cache; this.logger = logger; } #region IDeliverySend接口实现 public async Task CreateOrder(ExpressDeliveryOrder order) { SFCreateOrderDto sFSend = new SFCreateOrderDto() { orderId = order.Id.ToString(), language = "zh_CN", cargoDetails = new List() { new CargoDetailsItem() { count = (int)order.KDNum, name = order.GOODSNAME } } }; if (order.SettleAccountsTypeCode == "2") { if (string.IsNullOrWhiteSpace(order.MonthlyCard)) { throw Oops.Bah("当结费类型为月结时,月结卡号不能为空"); } sFSend.monthlyCard = order.MonthlyCard; } List contactList = new() { new ContactInfoListItem { address = $"{order.FJProvince}{order.FJCity}{order.FJAddress}", contact = order.FJPeople, contactType = 1, country = "CN", tel = order.FJTel, }, new ContactInfoListItem { address = $"{order.SJProvince}{order.SJCity}{order.SJAddress}", contact = order.SJPeople, contactType = 2, country = "CN", tel = order.SJTel, } }; sFSend.contactInfoList = contactList; (string url, object body) apiParam = await BuildApiParamAsync("EXP_RECE_CREATE_ORDER", sFSend); logger.LogInformation("调用顺丰下单接口,参数:" + apiParam.body.ToJson()); var strRtn = await apiParam.url.SetBody(apiParam.body, "application/x-www-form-urlencoded").PostAsStringAsync(); logger.LogInformation("调用顺丰下单接口,返回:" + strRtn); var jobj = strRtn.ToJObject(); if (jobj.GetStringValue("apiResultCode") == "A1000") { var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject(); bool isSuccess = resultDataObj.GetBooleanValue("success"); if (isSuccess) { var waybillNoInfoObj = resultDataObj.GetJObjectValue("msgData").GetJArrayValue("waybillNoInfoList")[0].Value(); var waybillNo = waybillNoInfoObj.GetStringValue("waybillNo"); return waybillNo; } else { var errorMsg = resultDataObj.GetStringValue("errorMsg"); logger.LogInformation("调用顺丰下单接口,返回异常,errorMsg:" + errorMsg); throw Oops.Bah(errorMsg); } } else { var apiErrorMsg = jobj.GetStringValue("apiErrorMsg"); logger.LogInformation("调用顺丰下单接口,返回异常,apiErrorMsg:" + apiErrorMsg); throw Oops.Bah(apiErrorMsg); } } public async Task CancelOrder(string orderId) { var param = new { dealType = 2, orderId }; (string url, object body) apiParam = await BuildApiParamAsync("EXP_RECE_UPDATE_ORDER", param); logger.LogInformation($"调用顺丰订单取消接口,参数:{apiParam.body.ToJson()}"); var strRtn = await apiParam.url.SetBody(apiParam.body, "application/x-www-form-urlencoded").PostAsStringAsync(); logger.LogInformation($"调用顺丰订单取消接口,返回:{strRtn}"); var jobj = strRtn.ToJObject(); if (jobj.GetStringValue("apiResultCode") == "A1000") { var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject(); if (resultDataObj.GetBooleanValue("success")) { return true; } else { var errorMsg = resultDataObj.GetStringValue("errorMsg"); logger.LogInformation($"顺丰取消快递下单接口,返回异常,errorMsg:{errorMsg}"); throw Oops.Bah(errorMsg); } } else { var apiErrorMsg = jobj.GetStringValue("apiErrorMsg"); logger.LogInformation($"顺丰取消快递下单接口,返回异常:{apiErrorMsg}"); throw Oops.Bah(apiErrorMsg); } } public async Task DownloadWaybillFile(string waybillNo, string savePath) { logger.LogInformation($"调用顺丰面单PDF下载接口开始..."); var waybillDownloadInfo = await GetWaybillDownloadInfo(waybillNo); var header = new Dictionary() { { "X-Auth-token", waybillDownloadInfo.Token } }; using var stream = await waybillDownloadInfo.Url.SetHeaders(header).GetAsStreamAsync(); using FileStream fs = new FileStream(savePath, FileMode.Create); await stream.CopyToAsync(fs); logger.LogInformation($"顺丰面单PDF文件保存完成,保存地址:{savePath}"); } #endregion /// /// 请求获取顺丰面单Pdf下载地址及访问Token /// /// private async Task<(string Url, string Token)> GetWaybillDownloadInfo(string masterWaybillNo) { SFPrintWaybillDto sFSend = new SFPrintWaybillDto() { fileType = "pdf", sync = true, templateCode = "fm_150_standard_DSWYR7LUN7FB", version = "2.0", documents = new List() { new SFPrintWaybillDto.Document(masterWaybillNo) } }; (string url, object body) apiParam = await BuildApiParamAsync("COM_RECE_CLOUD_PRINT_WAYBILLS", sFSend); logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,参数:" + apiParam.body.ToJson()); var strRtn = await apiParam.url.SetBody(apiParam.body, "application/x-www-form-urlencoded").PostAsStringAsync(); logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,返回:" + strRtn); var jobj = strRtn.ToJObject(); if (jobj.GetStringValue("apiResultCode") == "A1000") { var resultDataObj = jobj.GetStringValue("apiResultData").ToJObject(); bool isSuccess = resultDataObj.GetBooleanValue("success"); if (isSuccess) { var fileObj = resultDataObj.GetJObjectValue("obj").GetJArrayValue("files")[0].Value(); var token = fileObj.GetStringValue("token"); var url = fileObj.GetStringValue("url"); return (url, token); } else { var errorMsg = resultDataObj.GetStringValue("errorMsg"); logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,返回异常,errorMsg:" + errorMsg); throw Oops.Bah(errorMsg); } } else { var apiErrorMsg = jobj.GetStringValue("apiErrorMsg"); logger.LogInformation("调用顺丰获取顺丰面单Pdf下载地址接口,返回异常,apiErrorMsg:" + apiErrorMsg); throw Oops.Bah(apiErrorMsg); } } /// /// 构建请求顺丰接口所需的Url及Body参数 /// /// 接口编码 /// 参数 /// private async Task<(string url, object body)> BuildApiParamAsync(string apiCode, object businessData) { var cacheDictData = await cache.GetAllDictData(); var url = cacheDictData.FirstOrDefault(x => x.TypeCode == "url_set" && x.Code == "sf_api_url")?.Value; if (string.IsNullOrEmpty(url)) { throw Oops.Bah("需配置顺丰Api接口字典参数:url地址(sf_api_url)"); } var partnerId = cacheDictData.FirstOrDefault(x => x.TypeCode == "SFCode" && x.Code == "partnerId")?.Value; if (string.IsNullOrEmpty(partnerId)) { throw Oops.Bah("需配置顺丰Api接口字典参数:合作伙伴编码,即顾客编码(partnerId)"); } string accessToken; if (cache.Exists(SF_API_TOKEN_KEY)) { accessToken = cache.Get(SF_API_TOKEN_KEY); } else { var secret = cacheDictData.FirstOrDefault(x => x.TypeCode == "SFCode" && x.Code == "secret")?.Value; if (string.IsNullOrEmpty(partnerId)) { throw Oops.Bah("需配置顺丰Api接口字典参数:合作伙伴密钥,即校验码(secret)"); } var getTokenParam = new Dictionary() { { "partnerID",partnerId }, { "grantType", "password"}, { "secret", secret} }; logger.LogInformation("调用顺丰Token查询接口,参数:" + getTokenParam.ToJson()); var strRtn = await (url + "/oauth2/accessToken").SetBody(getTokenParam, "application/x-www-form-urlencoded").PostAsStringAsync(); logger.LogInformation("调用顺丰Token查询接口,返回:" + strRtn); var jobj = strRtn.ToJObject(); if (jobj.GetStringValue("apiResultCode") == "A1000") { accessToken = jobj.GetStringValue("accessToken"); //设置token过期时间2小时 await cache.SetTimeoutAsync(SF_API_TOKEN_KEY, accessToken, TimeSpan.FromHours(2)); } else { throw Oops.Bah("调用顺丰Token查询接口时发生异常:" + jobj.GetStringValue("apiErrorMsg")); } } var dict = new Dictionary() { { "partnerID", partnerId }, { "serviceCode", apiCode}, { "requestID", Guid.NewGuid().ToString()}, { "timestamp", DateTimeOffset.Now.ToUnixTimeSeconds()}, { "accessToken", accessToken}, { "msgData", businessData} }; return (url + "/std/service", dict); } } }