feat: openApi 统一信封与推费字段对齐,下单鉴权改请求头
All checks were successful
Build and Deploy / deploy (push) Successful in 56s
All checks were successful
Build and Deploy / deploy (push) Successful in 56s
推费入参对齐表字段;创建订单凭证走 appKey/appSecret Header,去掉上门揽收与 Body 内 customerId。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
a3b54b6b07
commit
87f4cebc9b
29
src/Oms.Application/Dtos/OpenApi/OpenApiEnvelope.cs
Normal file
29
src/Oms.Application/Dtos/OpenApi/OpenApiEnvelope.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
namespace Oms.Application.Dtos.OpenApi;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对外开放接口统一响应。
|
||||||
|
/// </summary>
|
||||||
|
public class OpenApiEnvelope<T>
|
||||||
|
{
|
||||||
|
public string Code { get; set; } = "200";
|
||||||
|
|
||||||
|
public string Msg { get; set; } = "success";
|
||||||
|
|
||||||
|
public T? Data { get; set; }
|
||||||
|
|
||||||
|
public static OpenApiEnvelope<T> Ok(T data) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Code = "200",
|
||||||
|
Msg = "success",
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
|
||||||
|
public static OpenApiEnvelope<T> Fail(string msg, string code = "400") =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Msg = msg,
|
||||||
|
Data = default
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,17 +1,149 @@
|
|||||||
|
using Oms.Domain.OrdersAggregate.Consts;
|
||||||
|
|
||||||
namespace Oms.Application.Dtos.Orders;
|
namespace Oms.Application.Dtos.Orders;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开放接口创建订单包裹:在标准下单入参上增加 AppKey/AppSecret(Body,非自定义 Header)。
|
/// 开放接口创建订单包裹入参:与单票字段对齐(不含上门揽收)。
|
||||||
|
/// 鉴权凭证放请求头 appKey / appSecret,勿放入 Body。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class OpenApiParcelCreateInput : OrderParcelCreateInput
|
public class OpenApiParcelCreateInput
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>请求标识(幂等,可选)。</summary>
|
||||||
/// 客户开放接口 AppKey。
|
public string? RequestId { get; set; }
|
||||||
/// </summary>
|
|
||||||
public string? AppKey { get; set; }
|
/// <summary>客户单号。</summary>
|
||||||
|
public string? ClientNo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>跟踪号(可选,系统通常忽略由下游回写)。</summary>
|
||||||
|
public string? TrackNo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>服务单号(可选,不传时系统自动生成)。</summary>
|
||||||
|
public string? ServiceNo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>产品编码(运输方式)。</summary>
|
||||||
|
public string? ProductCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>渠道编码(可选)。</summary>
|
||||||
|
public string? ChannelCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>货物属性(字典 cargo_attribute:general / electronics / sensitive)。</summary>
|
||||||
|
public string? CargoAttribute { get; set; }
|
||||||
|
|
||||||
|
/// <summary>出口报关方式(字典 export_customs_mode)。</summary>
|
||||||
|
public string? ExportCustomsMode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>出口资质备案 Id(9610 汇总申报时)。</summary>
|
||||||
|
public int? ExportQualificationId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>电商平台(9610 汇总申报时)。</summary>
|
||||||
|
public string? PlatformCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>仓库主键。</summary>
|
||||||
|
public int? WarehouseId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>长(cm)。</summary>
|
||||||
|
public decimal? Length { get; set; }
|
||||||
|
|
||||||
|
/// <summary>宽(cm)。</summary>
|
||||||
|
public decimal? Width { get; set; }
|
||||||
|
|
||||||
|
/// <summary>高(cm)。</summary>
|
||||||
|
public decimal? Height { get; set; }
|
||||||
|
|
||||||
|
/// <summary>实重(kg)。</summary>
|
||||||
|
public decimal? ActualWeight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>体积重(kg,可选)。</summary>
|
||||||
|
public decimal? VolumetricWeight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>计费重(kg,可选)。</summary>
|
||||||
|
public decimal? ChargeableWeight { get; set; }
|
||||||
|
|
||||||
|
/// <summary>体积(cm³,可选)。</summary>
|
||||||
|
public decimal? Cubic { get; set; }
|
||||||
|
|
||||||
|
/// <summary>是否 COD。</summary>
|
||||||
|
public bool IsCod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>COD 币种。</summary>
|
||||||
|
public string? CodCurrency { get; set; }
|
||||||
|
|
||||||
|
/// <summary>COD 金额。</summary>
|
||||||
|
public decimal? CodSum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>申报币种(字典 currency)。</summary>
|
||||||
|
public string? DeclaredCurrency { get; set; }
|
||||||
|
|
||||||
|
/// <summary>申报总金额(可选,不填可由明细汇总)。</summary>
|
||||||
|
public decimal? DeclaredSum { get; set; }
|
||||||
|
|
||||||
|
/// <summary>附加服务字典 code 列表。</summary>
|
||||||
|
public List<string>? AdditionalServices { get; set; }
|
||||||
|
|
||||||
|
/// <summary>是否偏远(可选预传)。</summary>
|
||||||
|
public byte? IsRemote { get; set; }
|
||||||
|
|
||||||
|
/// <summary>包裹数量。</summary>
|
||||||
|
public int? Quantity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>收件人。</summary>
|
||||||
|
public OrderParcelReceiverInput? Receiver { get; set; }
|
||||||
|
|
||||||
|
/// <summary>寄件人。</summary>
|
||||||
|
public OrderParcelSenderInput? Sender { get; set; }
|
||||||
|
|
||||||
|
/// <summary>申报明细。</summary>
|
||||||
|
public List<OrderParcelItemInput> Items { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>优惠券实例 Id(可选)。</summary>
|
||||||
|
public int? CouponInstanceId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>仅保存草稿,不预报计费。</summary>
|
||||||
|
public bool SaveAsDraft { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 客户开放接口 AppSecret。
|
/// 映射为内部单票创建入参。开放接口固定自发货(selfShip),客户 Id 由凭证解析后写入。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? AppSecret { get; set; }
|
public OrderParcelCreateInput ToOrderParcelCreateInput(int customerId) =>
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
RequestId = RequestId,
|
||||||
|
ClientNo = ClientNo,
|
||||||
|
TrackNo = TrackNo,
|
||||||
|
ServiceNo = ServiceNo,
|
||||||
|
ProductCode = ProductCode,
|
||||||
|
ChannelCode = ChannelCode,
|
||||||
|
CargoAttribute = CargoAttribute,
|
||||||
|
ExportCustomsMode = ExportCustomsMode,
|
||||||
|
ExportQualificationId = ExportQualificationId,
|
||||||
|
PlatformCode = PlatformCode,
|
||||||
|
WarehouseId = WarehouseId,
|
||||||
|
CustomerId = customerId,
|
||||||
|
Length = Length,
|
||||||
|
Width = Width,
|
||||||
|
Height = Height,
|
||||||
|
ActualWeight = ActualWeight,
|
||||||
|
VolumetricWeight = VolumetricWeight,
|
||||||
|
ChargeableWeight = ChargeableWeight,
|
||||||
|
Cubic = Cubic,
|
||||||
|
IsCod = IsCod,
|
||||||
|
CodCurrency = CodCurrency,
|
||||||
|
CodSum = CodSum,
|
||||||
|
DeclaredCurrency = DeclaredCurrency,
|
||||||
|
DeclaredSum = DeclaredSum,
|
||||||
|
AdditionalServices = AdditionalServices,
|
||||||
|
IsRemote = IsRemote,
|
||||||
|
Quantity = Quantity,
|
||||||
|
Receiver = Receiver,
|
||||||
|
Sender = Sender,
|
||||||
|
Items = Items ?? [],
|
||||||
|
CouponInstanceId = CouponInstanceId,
|
||||||
|
SaveAsDraft = SaveAsDraft,
|
||||||
|
InboundMethod = OrderInboundMethodCodes.SelfShip,
|
||||||
|
SenderAddressId = null,
|
||||||
|
ExpectPickupTime = null,
|
||||||
|
PickupDirection = null,
|
||||||
|
CarrierCode = null,
|
||||||
|
CarrierService = null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -344,10 +344,6 @@ public class PickupFeeLineOutput
|
|||||||
|
|
||||||
public class PickupCarrierQuoteOutput
|
public class PickupCarrierQuoteOutput
|
||||||
{
|
{
|
||||||
public string? ServiceMode { get; set; }
|
|
||||||
|
|
||||||
public string? PayMode { get; set; }
|
|
||||||
|
|
||||||
public List<PickupCarrierValueFeeOutput> ValueInfoList { get; set; } = [];
|
public List<PickupCarrierValueFeeOutput> ValueInfoList { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,7 +360,7 @@ public class PickupOpenApiPushFeeResult
|
|||||||
{
|
{
|
||||||
public string PickupNo { get; set; } = string.Empty;
|
public string PickupNo { get; set; } = string.Empty;
|
||||||
|
|
||||||
public decimal ActualWeight { get; set; }
|
public decimal TotalActualWeight { get; set; }
|
||||||
|
|
||||||
public decimal ChargeableWeight { get; set; }
|
public decimal ChargeableWeight { get; set; }
|
||||||
|
|
||||||
@ -372,31 +368,34 @@ public class PickupOpenApiPushFeeResult
|
|||||||
|
|
||||||
public decimal ReceivableAmount { get; set; }
|
public decimal ReceivableAmount { get; set; }
|
||||||
|
|
||||||
public decimal PayableAmount { get; set; }
|
public decimal? CarrierPayableAmount { get; set; }
|
||||||
|
|
||||||
public decimal? CarrierQuotedFreightAmount { get; set; }
|
public decimal? CarrierQuotedFreightAmount { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 承运推送件重尺入参(字段名对齐 t_pickup_order)。
|
||||||
|
/// </summary>
|
||||||
public class PickupOpenApiPushFeeInput
|
public class PickupOpenApiPushFeeInput
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>承运运单号。</summary>
|
||||||
/// 承运运单号,对应库字段 CarrierWaybillNo。
|
public string? CarrierWaybillNo { get; set; }
|
||||||
/// </summary>
|
|
||||||
public string? WaybillNumber { get; set; }
|
|
||||||
|
|
||||||
public decimal ActualWeight { get; set; }
|
/// <summary>实重合计。</summary>
|
||||||
|
public decimal TotalActualWeight { get; set; }
|
||||||
|
|
||||||
public decimal? FreightWeight { get; set; }
|
/// <summary>承运计费重(对照)。</summary>
|
||||||
|
public decimal? CarrierFreightWeight { get; set; }
|
||||||
|
|
||||||
public decimal? TotalFreightAmount { get; set; }
|
/// <summary>承运报价运费合计(对照)。</summary>
|
||||||
|
public decimal? CarrierQuotedFreightAmount { get; set; }
|
||||||
|
|
||||||
public decimal? ReceivableAmount { get; set; }
|
/// <summary>承运报文应收(对照)。</summary>
|
||||||
|
public decimal? CarrierQuotedReceivableAmount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>承运附加费明细(写入 CarrierQuoteJson)。</summary>
|
||||||
public List<PickupCarrierValueFeeOutput>? ValueInfoList { get; set; }
|
public List<PickupCarrierValueFeeOutput>? ValueInfoList { get; set; }
|
||||||
|
|
||||||
public List<PickupPackageSizeOutput>? GoodsSizeList { get; set; }
|
/// <summary>尺寸明细(写入 PackageSizesJson)。</summary>
|
||||||
|
public List<PickupPackageSizeOutput>? PackageSizes { get; set; }
|
||||||
public string? ServiceMode { get; set; }
|
|
||||||
|
|
||||||
public string? PayMode { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Oms.Application.Dtos.OpenApi;
|
||||||
using Oms.Application.Dtos.Orders;
|
using Oms.Application.Dtos.Orders;
|
||||||
using Oms.Application.Services.Orders;
|
using Oms.Application.Services.Orders;
|
||||||
using Oms.Application.Shared;
|
using Oms.Application.Shared;
|
||||||
@ -9,7 +10,8 @@ namespace Oms.Application.Services.OpenApi;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 对外开放 API(Swagger 文档分组:openApi)。
|
/// 对外开放 API(Swagger 文档分组:openApi)。
|
||||||
/// 客户机机:AppKey/AppSecret 放在 JSON Body;供应商推费由上游 itfx 鉴权,OMS 不再验 key。
|
/// 统一响应:{ code, msg, data }。
|
||||||
|
/// 客户机机鉴权:请求头 appKey + appSecret(勿放 Body,避免落日志)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiExplorerSettings(GroupName = "openApi")]
|
[ApiExplorerSettings(GroupName = "openApi")]
|
||||||
public class OpenApiService : ApplicationService
|
public class OpenApiService : ApplicationService
|
||||||
@ -24,29 +26,58 @@ public class OpenApiService : ApplicationService
|
|||||||
LazyServiceProvider.GetRequiredService<OrderService>();
|
LazyServiceProvider.GetRequiredService<OrderService>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 推送揽收件重尺并按当前产品计算应收应付(上游 itfx 鉴权后转发,无需 AppKey)。
|
/// 推送揽收件重尺并按当前产品计算应收应付(上游 itfx 鉴权后转发,无需客户凭证)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("/openapi/pickup-order/push-fee")]
|
[HttpPost("/openapi/pickup-order/push-fee")]
|
||||||
public Task<PickupOpenApiPushFeeResult> PushFeeAsync([FromBody] PickupOpenApiPushFeeInput input) =>
|
public async Task<OpenApiEnvelope<PickupOpenApiPushFeeResult>> PushFeeAsync(
|
||||||
FeePush.PushFeeAsync(input);
|
[FromBody] PickupOpenApiPushFeeInput input)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = await FeePush.PushFeeAsync(input);
|
||||||
|
return OpenApiEnvelope<PickupOpenApiPushFeeResult>.Ok(data);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
return OpenApiEnvelope<PickupOpenApiPushFeeResult>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return OpenApiEnvelope<PickupOpenApiPushFeeResult>.Fail(
|
||||||
|
string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message,
|
||||||
|
"500");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建订单包裹(客户机机:Body 传 AppKey、AppSecret)。
|
/// 创建订单包裹(仅自发货)。
|
||||||
|
/// 鉴权:Header appKey、appSecret;客户归属强制取自凭证,Body 不可指定 customerId。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[HttpPost("/openapi/parcel/create")]
|
[HttpPost("/openapi/parcel/create")]
|
||||||
public async Task<OrderParcelCreateOutput> CreateParcelAsync([FromBody] OpenApiParcelCreateInput input)
|
public async Task<OpenApiEnvelope<OrderParcelCreateOutput>> CreateParcelAsync(
|
||||||
|
[FromHeader(Name = CustomerOpenApiAuthHelper.AppKeyHeaderName)] string? appKey,
|
||||||
|
[FromHeader(Name = CustomerOpenApiAuthHelper.AppSecretHeaderName)] string? appSecret,
|
||||||
|
[FromBody] OpenApiParcelCreateInput input)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(input);
|
try
|
||||||
|
|
||||||
var (customer, _) = await AuthHelper.ResolveAsync(input.AppKey, input.AppSecret);
|
|
||||||
if (input.CustomerId is > 0 && input.CustomerId.Value != customer.Id)
|
|
||||||
{
|
{
|
||||||
throw new BusinessException("凭证无效");
|
ArgumentNullException.ThrowIfNull(input);
|
||||||
}
|
|
||||||
|
|
||||||
input.CustomerId = customer.Id;
|
var (customer, _) = await AuthHelper.ResolveAsync(appKey, appSecret);
|
||||||
return await OrderService.CreateParcelAsync(input);
|
var data = await OrderService.CreateParcelAsync(input.ToOrderParcelCreateInput(customer.Id));
|
||||||
|
return OpenApiEnvelope<OrderParcelCreateOutput>.Ok(data);
|
||||||
|
}
|
||||||
|
catch (BusinessException ex)
|
||||||
|
{
|
||||||
|
return OpenApiEnvelope<OrderParcelCreateOutput>.Fail(ex.Message);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return OpenApiEnvelope<OrderParcelCreateOutput>.Fail(
|
||||||
|
string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message,
|
||||||
|
"500");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,21 +51,21 @@ public class PickupOrderFeePushService : IScopedDependency
|
|||||||
throw new BusinessException("请求体不能为空");
|
throw new BusinessException("请求体不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
var carrierWaybillNo = (input.WaybillNumber ?? string.Empty).Trim();
|
var carrierWaybillNo = (input.CarrierWaybillNo ?? string.Empty).Trim();
|
||||||
if (string.IsNullOrWhiteSpace(carrierWaybillNo))
|
if (string.IsNullOrWhiteSpace(carrierWaybillNo))
|
||||||
{
|
{
|
||||||
throw new BusinessException("waybillNumber(承运运单号 CarrierWaybillNo)不能为空");
|
throw new BusinessException("carrierWaybillNo(承运运单号)不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.ActualWeight <= 0)
|
if (input.TotalActualWeight <= 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException("actualWeight 必须大于 0");
|
throw new BusinessException("totalActualWeight 必须大于 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
var sizes = NormalizeSizes(input.GoodsSizeList);
|
var sizes = NormalizeSizes(input.PackageSizes);
|
||||||
if (sizes.Count == 0)
|
if (sizes.Count == 0)
|
||||||
{
|
{
|
||||||
throw new BusinessException("goodsSizeList 不能为空");
|
throw new BusinessException("packageSizes 不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
var pickup = await _pickupOrderRepository.GetByCarrierWaybillNoAsync(carrierWaybillNo)
|
var pickup = await _pickupOrderRepository.GetByCarrierWaybillNoAsync(carrierWaybillNo)
|
||||||
@ -97,7 +97,7 @@ public class PickupOrderFeePushService : IScopedDependency
|
|||||||
Length = totalVolume,
|
Length = totalVolume,
|
||||||
Width = 1,
|
Width = 1,
|
||||||
Height = 1,
|
Height = 1,
|
||||||
ActualWeight = input.ActualWeight,
|
ActualWeight = input.TotalActualWeight,
|
||||||
Cubic = ParcelBillingCalculator.CalculateCubic(totalVolume, 1, 1)
|
Cubic = ParcelBillingCalculator.CalculateCubic(totalVolume, 1, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -184,17 +184,15 @@ public class PickupOrderFeePushService : IScopedDependency
|
|||||||
var freight = receivableFees.FirstOrDefault()?.Amount ?? receivableTotal;
|
var freight = receivableFees.FirstOrDefault()?.Amount ?? receivableTotal;
|
||||||
var other = Math.Max(0, receivableTotal - freight);
|
var other = Math.Max(0, receivableTotal - freight);
|
||||||
|
|
||||||
pickup.TotalActualWeight = input.ActualWeight;
|
pickup.TotalActualWeight = input.TotalActualWeight;
|
||||||
pickup.VolumetricWeight = volumetricWeight;
|
pickup.VolumetricWeight = volumetricWeight;
|
||||||
pickup.ChargeableWeight = chargeableWeight;
|
pickup.ChargeableWeight = chargeableWeight;
|
||||||
pickup.PackageSizesJson = OmsDocHelper.SerializeJson(sizes);
|
pickup.PackageSizesJson = OmsDocHelper.SerializeJson(sizes);
|
||||||
pickup.CarrierFreightWeight = input.FreightWeight;
|
pickup.CarrierFreightWeight = input.CarrierFreightWeight;
|
||||||
pickup.CarrierQuotedFreightAmount = input.TotalFreightAmount;
|
pickup.CarrierQuotedFreightAmount = input.CarrierQuotedFreightAmount;
|
||||||
pickup.CarrierQuotedReceivableAmount = input.ReceivableAmount;
|
pickup.CarrierQuotedReceivableAmount = input.CarrierQuotedReceivableAmount;
|
||||||
pickup.CarrierQuoteJson = OmsDocHelper.SerializeJson(new PickupCarrierQuoteOutput
|
pickup.CarrierQuoteJson = OmsDocHelper.SerializeJson(new PickupCarrierQuoteOutput
|
||||||
{
|
{
|
||||||
ServiceMode = input.ServiceMode,
|
|
||||||
PayMode = input.PayMode,
|
|
||||||
ValueInfoList = input.ValueInfoList ?? []
|
ValueInfoList = input.ValueInfoList ?? []
|
||||||
});
|
});
|
||||||
pickup.ReceivableFeeJson = OmsDocHelper.SerializeJson(receivableFees);
|
pickup.ReceivableFeeJson = OmsDocHelper.SerializeJson(receivableFees);
|
||||||
@ -210,12 +208,12 @@ public class PickupOrderFeePushService : IScopedDependency
|
|||||||
return new PickupOpenApiPushFeeResult
|
return new PickupOpenApiPushFeeResult
|
||||||
{
|
{
|
||||||
PickupNo = pickup.PickupNo,
|
PickupNo = pickup.PickupNo,
|
||||||
ActualWeight = input.ActualWeight,
|
TotalActualWeight = input.TotalActualWeight,
|
||||||
ChargeableWeight = chargeableWeight,
|
ChargeableWeight = chargeableWeight,
|
||||||
VolumetricWeight = volumetricWeight,
|
VolumetricWeight = volumetricWeight,
|
||||||
ReceivableAmount = receivableTotal,
|
ReceivableAmount = receivableTotal,
|
||||||
PayableAmount = payableTotal,
|
CarrierPayableAmount = payableTotal,
|
||||||
CarrierQuotedFreightAmount = input.TotalFreightAmount
|
CarrierQuotedFreightAmount = input.CarrierQuotedFreightAmount
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using Fake.Application;
|
using Fake.Application;
|
||||||
using Fake.DependencyInjection;
|
using Fake.DependencyInjection;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
@ -8,10 +10,13 @@ using Oms.Domain.CustomersAggregate.Repositories;
|
|||||||
namespace Oms.Application.Shared;
|
namespace Oms.Application.Shared;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 客户 OpenAPI 机机鉴权:按 AppKey+AppSecret 解析主客户,供 whoami 及后续业务接口复用。
|
/// 客户 OpenAPI 机机鉴权:按请求头 AppKey + AppSecret 解析主客户。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CustomerOpenApiAuthHelper : IScopedDependency
|
public class CustomerOpenApiAuthHelper : IScopedDependency
|
||||||
{
|
{
|
||||||
|
public const string AppKeyHeaderName = "appKey";
|
||||||
|
public const string AppSecretHeaderName = "appSecret";
|
||||||
|
|
||||||
private readonly ICustomerOpenApiCredentialRepository _credentialRepository;
|
private readonly ICustomerOpenApiCredentialRepository _credentialRepository;
|
||||||
private readonly ICustomerRepository _customerRepository;
|
private readonly ICustomerRepository _customerRepository;
|
||||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
@ -26,6 +31,19 @@ public class CustomerOpenApiAuthHelper : IScopedDependency
|
|||||||
_httpContextAccessor = httpContextAccessor;
|
_httpContextAccessor = httpContextAccessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从当前请求头读取 appKey / appSecret 并鉴权。
|
||||||
|
/// </summary>
|
||||||
|
public Task<(Customer Customer, CustomerOpenApiCredential Credential)> ResolveFromHeadersAsync(
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var http = _httpContextAccessor.HttpContext
|
||||||
|
?? throw new BusinessException("凭证无效");
|
||||||
|
var appKey = http.Request.Headers[AppKeyHeaderName].FirstOrDefault();
|
||||||
|
var appSecret = http.Request.Headers[AppSecretHeaderName].FirstOrDefault();
|
||||||
|
return ResolveAsync(appKey, appSecret, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<(Customer Customer, CustomerOpenApiCredential Credential)> ResolveAsync(
|
public async Task<(Customer Customer, CustomerOpenApiCredential Credential)> ResolveAsync(
|
||||||
string? appKey,
|
string? appKey,
|
||||||
string? appSecret,
|
string? appSecret,
|
||||||
@ -41,7 +59,7 @@ public class CustomerOpenApiAuthHelper : IScopedDependency
|
|||||||
var credential = await _credentialRepository.GetByAppKeyAsync(key, cancellationToken);
|
var credential = await _credentialRepository.GetByAppKeyAsync(key, cancellationToken);
|
||||||
if (credential == null
|
if (credential == null
|
||||||
|| !credential.Enabled
|
|| !credential.Enabled
|
||||||
|| !string.Equals(credential.AppSecret, secret, StringComparison.Ordinal))
|
|| !FixedTimeEqualsUtf8(credential.AppSecret, secret))
|
||||||
{
|
{
|
||||||
throw new BusinessException("凭证无效");
|
throw new BusinessException("凭证无效");
|
||||||
}
|
}
|
||||||
@ -63,6 +81,19 @@ public class CustomerOpenApiAuthHelper : IScopedDependency
|
|||||||
return (customer, credential);
|
return (customer, credential);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool FixedTimeEqualsUtf8(string? expected, string actual)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(expected))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var left = Encoding.UTF8.GetBytes(expected);
|
||||||
|
var right = Encoding.UTF8.GetBytes(actual);
|
||||||
|
return left.Length == right.Length
|
||||||
|
&& CryptographicOperations.FixedTimeEquals(left, right);
|
||||||
|
}
|
||||||
|
|
||||||
private string? ResolveClientIp()
|
private string? ResolveClientIp()
|
||||||
{
|
{
|
||||||
var http = _httpContextAccessor.HttpContext;
|
var http = _httpContextAccessor.HttpContext;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user