20261008-ide1.0发布 #1

Merged
zzh merged 42 commits from test into master 2026-10-08 09:18:00 +08:00
9 changed files with 73 additions and 260 deletions
Showing only changes of commit a3b54b6b07 - Show all commits

View File

@ -41,26 +41,3 @@ public class CustomerOpenApiCredentialAdminSetEnabledInput : CustomerOpenApiCred
{ {
public bool Enabled { get; set; } public bool Enabled { get; set; }
} }
public class CustomerOpenApiWhoAmIInput
{
public string? AppSecret { get; set; }
}
public class CustomerOpenApiWhoAmIResult
{
public int CustomerId { get; set; }
public string CustomerCode { get; set; } = string.Empty;
public string CustomerName { get; set; } = string.Empty;
}
public class CustomerOpenApiEnvelope
{
public string Code { get; set; } = "200";
public string Msg { get; set; } = "成功";
public CustomerOpenApiWhoAmIResult? Data { get; set; }
}

View File

@ -0,0 +1,17 @@
namespace Oms.Application.Dtos.Orders;
/// <summary>
/// 开放接口创建订单包裹:在标准下单入参上增加 AppKey/AppSecret(Body,非自定义 Header)。
/// </summary>
public class OpenApiParcelCreateInput : OrderParcelCreateInput
{
/// <summary>
/// 客户开放接口 AppKey。
/// </summary>
public string? AppKey { get; set; }
/// <summary>
/// 客户开放接口 AppSecret。
/// </summary>
public string? AppSecret { get; set; }
}

View File

@ -360,15 +360,6 @@ public class PickupCarrierValueFeeOutput
public decimal? FeeAmount { get; set; } public decimal? FeeAmount { get; set; }
} }
public class PickupOpenApiEnvelope
{
public string Code { get; set; } = "200";
public string Msg { get; set; } = "成功";
public PickupOpenApiPushFeeResult? Data { get; set; }
}
public class PickupOpenApiPushFeeResult public class PickupOpenApiPushFeeResult
{ {
public string PickupNo { get; set; } = string.Empty; public string PickupNo { get; set; } = string.Empty;

View File

@ -1,63 +0,0 @@
using Fake.Application;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Customers;
using Oms.Application.Shared;
namespace Oms.Application.Services.Customers;
/// <summary>
/// 客户 OpenAPI 机机入口(AllowAnonymous,AppKey + AppSecret)。
/// </summary>
[ApiExplorerSettings(GroupName = "OMS-Customer")]
public class CustomerOpenApiService : ApplicationService
{
private CustomerOpenApiAuthHelper AuthHelper =>
LazyServiceProvider.GetRequiredService<CustomerOpenApiAuthHelper>();
/// <summary>
/// 探针:校验凭证并返回客户身份。
/// </summary>
[AllowAnonymous]
[HttpPost("/openapi/customer/whoami")]
public async Task<CustomerOpenApiEnvelope> WhoAmIAsync(
[FromQuery] string? appKey,
[FromHeader(Name = "appSecret")] string? appSecretHeader,
[FromBody] CustomerOpenApiWhoAmIInput? body)
{
try
{
var appSecret = !string.IsNullOrWhiteSpace(appSecretHeader)
? appSecretHeader
: body?.AppSecret;
var (customer, _) = await AuthHelper.ResolveAsync(appKey, appSecret);
return new CustomerOpenApiEnvelope
{
Code = "200",
Msg = "成功",
Data = new CustomerOpenApiWhoAmIResult
{
CustomerId = customer.Id,
CustomerCode = customer.CustomerCode,
CustomerName = customer.CustomerName
}
};
}
catch (BusinessException ex)
{
return Fail(ex.Message);
}
catch (Exception ex)
{
return Fail(string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message, "500");
}
}
private static CustomerOpenApiEnvelope Fail(string msg, string code = "401") =>
new()
{
Code = code,
Msg = msg,
Data = null
};
}

View File

@ -0,0 +1,52 @@
using Fake.Application;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Orders;
using Oms.Application.Services.Orders;
using Oms.Application.Shared;
namespace Oms.Application.Services.OpenApi;
/// <summary>
/// 对外开放 API(Swagger 文档分组:openApi)。
/// 客户机机:AppKey/AppSecret 放在 JSON Body;供应商推费由上游 itfx 鉴权,OMS 不再验 key。
/// </summary>
[ApiExplorerSettings(GroupName = "openApi")]
public class OpenApiService : ApplicationService
{
private CustomerOpenApiAuthHelper AuthHelper =>
LazyServiceProvider.GetRequiredService<CustomerOpenApiAuthHelper>();
private PickupOrderFeePushService FeePush =>
LazyServiceProvider.GetRequiredService<PickupOrderFeePushService>();
private OrderService OrderService =>
LazyServiceProvider.GetRequiredService<OrderService>();
/// <summary>
/// 推送揽收件重尺并按当前产品计算应收应付(上游 itfx 鉴权后转发,无需 AppKey)。
/// </summary>
[AllowAnonymous]
[HttpPost("/openapi/pickup-order/push-fee")]
public Task<PickupOpenApiPushFeeResult> PushFeeAsync([FromBody] PickupOpenApiPushFeeInput input) =>
FeePush.PushFeeAsync(input);
/// <summary>
/// 创建订单包裹(客户机机:Body 传 AppKey、AppSecret)。
/// </summary>
[AllowAnonymous]
[HttpPost("/openapi/parcel/create")]
public async Task<OrderParcelCreateOutput> CreateParcelAsync([FromBody] OpenApiParcelCreateInput input)
{
ArgumentNullException.ThrowIfNull(input);
var (customer, _) = await AuthHelper.ResolveAsync(input.AppKey, input.AppSecret);
if (input.CustomerId is > 0 && input.CustomerId.Value != customer.Id)
{
throw new BusinessException("凭证无效");
}
input.CustomerId = customer.Id;
return await OrderService.CreateParcelAsync(input);
}
}

View File

@ -1,76 +0,0 @@
using Fake.Application;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Orders;
namespace Oms.Application.Services.Orders;
/// <summary>
/// 国内揽收开放接口(承运商推送件重尺)。
/// </summary>
[ApiExplorerSettings(GroupName = "OMS-Order")]
public class PickupOpenApiService : ApplicationService
{
private PickupOrderFeePushService FeePush =>
LazyServiceProvider.GetRequiredService<PickupOrderFeePushService>();
/// <summary>
/// 开放平台路由:method=open.api.openCommon.pushFee,仅校验 appKey。
/// </summary>
[AllowAnonymous]
[HttpPost("/openapi")]
public Task<PickupOpenApiEnvelope> InvokeAsync(
[FromQuery] string? appKey,
[FromQuery] string? method,
[FromBody] PickupOpenApiPushFeeInput? body) =>
HandlePushFeeAsync(appKey, method, body);
/// <summary>
/// 推送揽收重量/尺寸并按当前产品计算应收应付。
/// </summary>
[AllowAnonymous]
[HttpPost("/openapi/openCommon/pushFee")]
public Task<PickupOpenApiEnvelope> PushFeeAsync(
[FromQuery] string? appKey,
[FromBody] PickupOpenApiPushFeeInput? body) =>
HandlePushFeeAsync(appKey, PickupOrderFeePushService.PushFeeMethod, body);
private async Task<PickupOpenApiEnvelope> HandlePushFeeAsync(
string? appKey,
string? method,
PickupOpenApiPushFeeInput? body)
{
try
{
if (!string.IsNullOrWhiteSpace(method)
&& !string.Equals(method.Trim(), PickupOrderFeePushService.PushFeeMethod, StringComparison.OrdinalIgnoreCase))
{
return Fail("400", $"不支持的 method:{method}");
}
var data = await FeePush.PushFeeAsync(appKey, body);
return new PickupOpenApiEnvelope
{
Code = "200",
Msg = "成功",
Data = data
};
}
catch (BusinessException ex)
{
return Fail("400", ex.Message);
}
catch (Exception ex)
{
return Fail("500", string.IsNullOrWhiteSpace(ex.Message) ? "系统异常" : ex.Message);
}
}
private static PickupOpenApiEnvelope Fail(string code, string msg) =>
new()
{
Code = code,
Msg = msg,
Data = null
};
}

View File

@ -2,7 +2,6 @@ using Fake.Application;
using Fake.DependencyInjection; using Fake.DependencyInjection;
using Oms.Application.Dtos.Channels; using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Orders; using Oms.Application.Dtos.Orders;
using Oms.Application.Integrations.Itfx;
using Oms.Application.Services.Orders.Issues; using Oms.Application.Services.Orders.Issues;
using Oms.Application.Shared; using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Entities; using Oms.Domain.ChannelsAggregate.Entities;
@ -10,7 +9,6 @@ using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities; using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories; using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.SystemAggregate.Consts;
using Oms.Domain.SystemAggregate.Repositories; using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.Orders; namespace Oms.Application.Services.Orders;
@ -20,51 +18,34 @@ namespace Oms.Application.Services.Orders;
/// </summary> /// </summary>
public class PickupOrderFeePushService : IScopedDependency public class PickupOrderFeePushService : IScopedDependency
{ {
public const string PushFeeMethod = "open.api.openCommon.pushFee";
private readonly IPickupOrderRepository _pickupOrderRepository; private readonly IPickupOrderRepository _pickupOrderRepository;
private readonly ISystemOtherConfigRepository _otherConfigRepository;
private readonly ISalesProductRepository _salesProductRepository; private readonly ISalesProductRepository _salesProductRepository;
private readonly IServiceChannelRepository _serviceChannelRepository; private readonly IServiceChannelRepository _serviceChannelRepository;
private readonly IServiceChannelGroupRepository _serviceChannelGroupRepository;
private readonly IWeightRuleRepository _weightRuleRepository; private readonly IWeightRuleRepository _weightRuleRepository;
private readonly IWeightRoundingRepository _weightRoundingRepository; private readonly IWeightRoundingRepository _weightRoundingRepository;
private readonly IKpiAreaRepository _kpiAreaRepository; private readonly IKpiAreaRepository _kpiAreaRepository;
private readonly IWarehouseAddressRepository _warehouseAddressRepository; private readonly IWarehouseAddressRepository _warehouseAddressRepository;
private readonly IItfxMetadataClient _itfxMetadataClient;
public PickupOrderFeePushService( public PickupOrderFeePushService(
IPickupOrderRepository pickupOrderRepository, IPickupOrderRepository pickupOrderRepository,
ISystemOtherConfigRepository otherConfigRepository,
ISalesProductRepository salesProductRepository, ISalesProductRepository salesProductRepository,
IServiceChannelRepository serviceChannelRepository, IServiceChannelRepository serviceChannelRepository,
IServiceChannelGroupRepository serviceChannelGroupRepository,
IWeightRuleRepository weightRuleRepository, IWeightRuleRepository weightRuleRepository,
IWeightRoundingRepository weightRoundingRepository, IWeightRoundingRepository weightRoundingRepository,
IKpiAreaRepository kpiAreaRepository, IKpiAreaRepository kpiAreaRepository,
IWarehouseAddressRepository warehouseAddressRepository, IWarehouseAddressRepository warehouseAddressRepository)
IItfxMetadataClient itfxMetadataClient)
{ {
_pickupOrderRepository = pickupOrderRepository; _pickupOrderRepository = pickupOrderRepository;
_otherConfigRepository = otherConfigRepository;
_salesProductRepository = salesProductRepository; _salesProductRepository = salesProductRepository;
_serviceChannelRepository = serviceChannelRepository; _serviceChannelRepository = serviceChannelRepository;
_serviceChannelGroupRepository = serviceChannelGroupRepository;
_weightRuleRepository = weightRuleRepository; _weightRuleRepository = weightRuleRepository;
_weightRoundingRepository = weightRoundingRepository; _weightRoundingRepository = weightRoundingRepository;
_kpiAreaRepository = kpiAreaRepository; _kpiAreaRepository = kpiAreaRepository;
_warehouseAddressRepository = warehouseAddressRepository; _warehouseAddressRepository = warehouseAddressRepository;
_itfxMetadataClient = itfxMetadataClient;
} }
public async Task<PickupOpenApiPushFeeResult> PushFeeAsync(string? appKey, PickupOpenApiPushFeeInput? input) public async Task<PickupOpenApiPushFeeResult> PushFeeAsync(PickupOpenApiPushFeeInput? input)
{ {
var key = (appKey ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(key))
{
throw new BusinessException("appKey 不能为空");
}
if (input == null) if (input == null)
{ {
throw new BusinessException("请求体不能为空"); throw new BusinessException("请求体不能为空");
@ -90,8 +71,6 @@ public class PickupOrderFeePushService : IScopedDependency
var pickup = await _pickupOrderRepository.GetByCarrierWaybillNoAsync(carrierWaybillNo) var pickup = await _pickupOrderRepository.GetByCarrierWaybillNoAsync(carrierWaybillNo)
?? throw new BusinessException($"揽收单不存在:承运运单号 {carrierWaybillNo}"); ?? throw new BusinessException($"揽收单不存在:承运运单号 {carrierWaybillNo}");
await EnsureAppKeyAsync(key, pickup);
if (pickup.PayStatus == PickupPayStatusCodes.Paid) if (pickup.PayStatus == PickupPayStatusCodes.Paid)
{ {
throw new BusinessException("揽收单已支付,拒绝修改件重尺与费用"); throw new BusinessException("揽收单已支付,拒绝修改件重尺与费用");
@ -240,67 +219,6 @@ public class PickupOrderFeePushService : IScopedDependency
}; };
} }
private async Task EnsureAppKeyAsync(string appKey, PickupOrder pickup)
{
var config = await _otherConfigRepository.GetByConfigCodeAsync(SystemOtherConfigCodes.PickupOpenApi);
if (config != null && config.Enabled && !string.IsNullOrWhiteSpace(config.AppKey))
{
if (!string.Equals(config.AppKey.Trim(), appKey, StringComparison.Ordinal))
{
throw new BusinessException("appKey 不正确");
}
return;
}
var expected = await ResolvePickupAppKeyAsync(pickup);
if (string.IsNullOrWhiteSpace(expected)
|| !string.Equals(expected, appKey, StringComparison.Ordinal))
{
throw new BusinessException("appKey 不正确");
}
}
private async Task<string?> ResolvePickupAppKeyAsync(PickupOrder pickup)
{
if (string.IsNullOrWhiteSpace(pickup.CarrierService))
{
return null;
}
var product = await _salesProductRepository.GetByProductCodeAsync(pickup.CarrierService.Trim());
if (product == null)
{
return null;
}
var providers = await _salesProductRepository.GetAllocationProvidersAsync(product.Id);
var channelId = providers
.Where(x => x.ServiceChannelId is > 0)
.OrderBy(x => x.SerialNumber)
.Select(x => x.ServiceChannelId!.Value)
.FirstOrDefault();
if (channelId <= 0)
{
return null;
}
var channel = await _serviceChannelRepository.FirstOrDefaultAsync(x => x.Id == channelId);
if (channel?.GroupId is not > 0)
{
return null;
}
var group = await _serviceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == channel.GroupId.Value);
if (group == null || group.AppId <= 0)
{
return null;
}
var app = await _itfxMetadataClient.GetAppAsync(group.AppId);
return app?.AppKey?.Trim();
}
private async Task<(decimal VolumetricWeight, decimal ChargeableWeight)> CalculateChargeableAsync( private async Task<(decimal VolumetricWeight, decimal ChargeableWeight)> CalculateChargeableAsync(
SalesProduct product, SalesProduct product,
OrderWeight weight) OrderWeight weight)

View File

@ -190,10 +190,7 @@ public class SystemOtherConfigService : ApplicationService
} }
else if (string.Equals(configCode, SystemOtherConfigCodes.PickupOpenApi, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(configCode, SystemOtherConfigCodes.PickupOpenApi, StringComparison.OrdinalIgnoreCase))
{ {
if (string.IsNullOrWhiteSpace(input.AppKey)) // 鉴权已改由 itfx 上游完成;本配置不再强制 AppKey
{
throw new BusinessException("揽收开放接口 AppKey 不能为空");
}
} }
else if (string.Equals(configCode, SystemOtherConfigCodes.WeChatMp, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(configCode, SystemOtherConfigCodes.WeChatMp, StringComparison.OrdinalIgnoreCase))
{ {

View File

@ -56,7 +56,7 @@ public static class SystemOtherConfigCodes
public const string ApiCenter = "api_center"; public const string ApiCenter = "api_center";
/// <summary> /// <summary>
/// 国内揽收服务商推送件重尺/费用:仅校验 AppKey(暂不验签)。 /// 国内揽收服务商推送件重尺/费用(历史配置;鉴权已改由 itfx 上游应用完成,OMS 不再校验 AppKey)。
/// </summary> /// </summary>
public const string PickupOpenApi = "pickup_open_api"; public const string PickupOpenApi = "pickup_open_api";
} }