admin/src/Oms.Application/Shared/OrderDownstreamCredentialResolver.cs
zzh fffecb5964 Fix downstream forecast: pass auth + cache itfx app secret
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 18:08:09 +08:00

180 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Fake.DependencyInjection;
using Fake.Application;
using Oms.Application.Dtos.Channels;
using Oms.Application.Integrations.Itfx;
using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Entities;
namespace Oms.Application.Shared;
public sealed class OrderDownstreamCredentials
{
public string SupplierCode { get; set; } = string.Empty;
public string AppKey { get; set; } = string.Empty;
public string AppSecret { get; set; } = string.Empty;
}
/// <summary>
/// 解析订单下游 itfx 凭证(优先订单绑定字段,回退渠道分组)。
/// </summary>
public class OrderDownstreamCredentialResolver(
IServiceChannelRepository serviceChannelRepository,
IServiceChannelGroupRepository serviceChannelGroupRepository,
IItfxMetadataClient itfxMetadataClient) : IScopedDependency
{
public async Task<OrderDownstreamCredentials> ResolveAsync(Order order, CancellationToken cancellationToken = default)
{
var fromSnapshot = await TryResolveFromChannelRulesSnapshotAsync(order, cancellationToken);
if (fromSnapshot != null)
{
return fromSnapshot;
}
if (!string.IsNullOrWhiteSpace(order.DownstreamSupplierCode)
&& !string.IsNullOrWhiteSpace(order.DownstreamAppKey)
&& order.DownstreamAppId is > 0)
{
var app = await itfxMetadataClient.GetAppAsync(order.DownstreamAppId.Value, cancellationToken);
var appSecret = app?.AppSecret?.Trim();
if (string.IsNullOrWhiteSpace(appSecret))
{
throw new BusinessException("应用 AppSecret 未配置");
}
return new OrderDownstreamCredentials
{
SupplierCode = order.DownstreamSupplierCode.Trim(),
AppKey = order.DownstreamAppKey.Trim(),
AppSecret = appSecret
};
}
if (string.IsNullOrWhiteSpace(order.ChannelCode))
{
throw new BusinessException("订单未绑定服务渠道,无法预报下游");
}
var channel = await serviceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim(), cancellationToken);
if (channel == null)
{
throw new BusinessException($"未找到渠道「{order.ChannelCode}」");
}
if (!channel.GroupId.HasValue || channel.GroupId.Value <= 0)
{
throw new BusinessException($"渠道「{order.ChannelCode}」未关联服务商分组");
}
var group = await serviceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == channel.GroupId.Value, cancellationToken);
if (group == null)
{
throw new BusinessException("服务商分组不存在或已删除");
}
if (group.AppId <= 0)
{
throw new BusinessException($"服务商「{group.Name}」未配置应用");
}
if (group.ApiServiceProviderId <= 0)
{
throw new BusinessException($"服务商「{group.Name}」未配置 API 服务商");
}
var appInfo = await itfxMetadataClient.GetAppAsync(group.AppId, cancellationToken);
if (appInfo == null || string.IsNullOrWhiteSpace(appInfo.AppKey) || string.IsNullOrWhiteSpace(appInfo.AppSecret))
{
throw new BusinessException("应用 AppKey / AppSecret 未配置");
}
var sp = await itfxMetadataClient.GetServiceProviderAsync(group.ApiServiceProviderId, cancellationToken);
if (sp == null || string.IsNullOrWhiteSpace(sp.Code))
{
throw new BusinessException("API 服务商编码(supplierCode)未配置");
}
return new OrderDownstreamCredentials
{
SupplierCode = sp.Code.Trim(),
AppKey = appInfo.AppKey.Trim(),
AppSecret = appInfo.AppSecret.Trim()
};
}
private async Task<OrderDownstreamCredentials?> TryResolveFromChannelRulesSnapshotAsync(
Order order,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(order.ChannelCode))
{
return null;
}
var channel = await serviceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim(), cancellationToken);
if (channel == null)
{
return null;
}
// 规则快照里固化了 itfx 应用信息(含 AppSecret)。
const string rulesOperatorType = "rules";
var bindTime = order.CreateTime == default ? DateTime.Now : order.CreateTime;
var record = await serviceChannelRepository.GetEffectivePublishRecordAsync(
channel.Id,
rulesOperatorType,
bindTime,
cancellationToken);
if (record == null || string.IsNullOrWhiteSpace(record.Content))
{
return null;
}
var snapshot = OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(record.Content);
var downstream = snapshot?.Downstream;
if (downstream == null)
{
return null;
}
// 订单绑定的 AppId 与快照不一致时,直接回退旧逻辑。
if (order.DownstreamAppId is > 0
&& downstream.AppId is > 0
&& order.DownstreamAppId.Value != downstream.AppId.Value)
{
return null;
}
var appSecret = downstream.AppSecret?.Trim();
if (string.IsNullOrWhiteSpace(appSecret))
{
return null;
}
var appKey = !string.IsNullOrWhiteSpace(order.DownstreamAppKey)
? order.DownstreamAppKey.Trim()
: downstream.AppKey?.Trim();
if (string.IsNullOrWhiteSpace(appKey))
{
return null;
}
var supplierCode = !string.IsNullOrWhiteSpace(order.DownstreamSupplierCode)
? order.DownstreamSupplierCode.Trim()
: downstream.SupplierCode?.Trim();
if (string.IsNullOrWhiteSpace(supplierCode))
{
return null;
}
return new OrderDownstreamCredentials
{
SupplierCode = supplierCode,
AppKey = appKey,
AppSecret = appSecret
};
}
}