diff --git a/scripts/debug-upsert-small-bag.ps1 b/scripts/debug-upsert-small-bag.ps1 index d25c40c..7256c29 100644 --- a/scripts/debug-upsert-small-bag.ps1 +++ b/scripts/debug-upsert-small-bag.ps1 @@ -24,9 +24,6 @@ $timestamp = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() $sign = Get-SmallBagSign -Key $AppKey -Ts $timestamp -Secret $AppSecret $body = @{ - appKey = $AppKey - timestamp = $timestamp - sign = $sign client = $Client orderNo = $OrderNo trackNo = "" @@ -43,7 +40,8 @@ $body = @{ } } | ConvertTo-Json -Depth 5 -Compress -$url = ($BaseUrl.TrimEnd('/')) + "/OrderV2/UpsertSmallBag" +$base = $BaseUrl.TrimEnd('/') +$url = "$base/OrderV2/UpsertSmallBag?appKey=$([uri]::EscapeDataString($AppKey))×tamp=$timestamp&sign=$([uri]::EscapeDataString($sign))" Write-Host "========== UpsertSmallBag Debug ==========" -ForegroundColor Cyan Write-Host "URL: $url" @@ -66,7 +64,7 @@ catch { $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream()) $errBody = $reader.ReadToEnd() Write-Host "HTTP $([int]$_.Exception.Response.StatusCode)" -ForegroundColor Red - if ($errBody) { Write-Host $errBody } else { Write-Host "(empty body — 多为签名/鉴权失败,请核对 appKey/appSecret 或联系轨迹平台)" } + if ($errBody) { Write-Host $errBody } else { Write-Host "(empty body — 请核对 appKey/appSecret 或联系轨迹平台)" } } else { Write-Host $_.Exception.Message -ForegroundColor Red diff --git a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs index bb708a9..c5a0589 100644 --- a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs +++ b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackOrderClient.cs @@ -66,15 +66,15 @@ internal sealed class SmallBagTrackOrderClient( var appKey = settings.AppKey.Trim(); var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var sign = SmallBagTrackSignHelper.GenerateSign(appKey, timestamp, settings.AppSecret.Trim()); - var payload = SmallBagTrackUpsertApiRequest.FromBusinessRequest(request, appKey, timestamp, sign); - var url = $"{settings.BaseUrl.Trim().TrimEnd('/')}/OrderV2/UpsertSmallBag"; - var requestJson = JsonSerializer.Serialize(payload, JsonOptions); + var body = SmallBagTrackUpsertApiRequest.FromBusinessRequest(request); + var url = SmallBagTrackUpsertApiRequest.BuildRequestUrl(settings.BaseUrl, appKey, timestamp, sign); + var requestJson = JsonSerializer.Serialize(body, JsonOptions); var client = httpClientFactory.CreateClient(HttpClientName); client.Timeout = TimeSpan.FromSeconds(Math.Max(5, settings.TimeoutSeconds)); - using var response = await client.PostAsJsonAsync(url, payload, cancellationToken); - var body = await response.Content.ReadAsStringAsync(cancellationToken); + using var response = await client.PostAsJsonAsync(url, body, cancellationToken); + var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); return new SmallBagTrackUpsertResult { Skipped = false, @@ -82,7 +82,7 @@ internal sealed class SmallBagTrackOrderClient( Url = url, RequestJson = requestJson, StatusCode = (int)response.StatusCode, - ResponseBody = body + ResponseBody = responseBody }; } diff --git a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackSignHelper.cs b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackSignHelper.cs index 87ee043..eaaf631 100644 --- a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackSignHelper.cs +++ b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackSignHelper.cs @@ -4,13 +4,26 @@ using System.Text; namespace Oms.Application.Integrations.SmallBagTrack; /// -/// 轨迹服务 API 签名:appKey={}&timestamp={}&appSecret={} → MD5 大写。 +/// 轨迹服务 API 签名(与轨迹平台官方算法一致)。 /// internal static class SmallBagTrackSignHelper { + /// + /// 生成 API 签名。 + /// + /// 应用唯一标识 + /// Unix 时间戳(秒) + /// 应用密钥 + /// MD5 签名(大写十六进制) public static string GenerateSign(string appKey, long timestamp, string appSecret) { - var signString = $"appKey={appKey}×tamp={timestamp}&appSecret={appSecret}"; + // 1. 排序后拼接:appKey, timestamp + var sortedParams = $"appKey={appKey}×tamp={timestamp}"; + + // 2. 拼接密钥 + var signString = $"{sortedParams}&appSecret={appSecret}"; + + // 3. MD5 大写 return CalculateMd5Hash(signString); } diff --git a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertApiRequest.cs b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertApiRequest.cs index f1bef48..e7e7d37 100644 --- a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertApiRequest.cs +++ b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertApiRequest.cs @@ -1,16 +1,10 @@ namespace Oms.Application.Integrations.SmallBagTrack; /// -/// POST /OrderV2/UpsertSmallBag 完整请求体(含公共参数 + 业务字段)。 +/// POST /OrderV2/UpsertSmallBag 业务请求体(公共参数 appKey/timestamp/sign 走 Query)。 /// internal sealed class SmallBagTrackUpsertApiRequest { - public string AppKey { get; set; } = string.Empty; - - public long Timestamp { get; set; } - - public string Sign { get; set; } = string.Empty; - public string Client { get; set; } = string.Empty; public string OrderNo { get; set; } = string.Empty; @@ -27,16 +21,9 @@ internal sealed class SmallBagTrackUpsertApiRequest public SmallBagTrackConsigneeRequest Consignee { get; set; } = new(); - public static SmallBagTrackUpsertApiRequest FromBusinessRequest( - SmallBagTrackUpsertRequest request, - string appKey, - long timestamp, - string sign) => + public static SmallBagTrackUpsertApiRequest FromBusinessRequest(SmallBagTrackUpsertRequest request) => new() { - AppKey = appKey, - Timestamp = timestamp, - Sign = sign, Client = request.Client, OrderNo = request.OrderNo, TrackNo = request.TrackNo, @@ -46,4 +33,15 @@ internal sealed class SmallBagTrackUpsertApiRequest ApiCode = request.ApiCode, Consignee = request.Consignee }; + + public static string BuildRequestUrl(string baseUrl, string appKey, long timestamp, string sign) + { + var root = $"{baseUrl.Trim().TrimEnd('/')}/OrderV2/UpsertSmallBag"; + var query = string.Join( + '&', + $"appKey={Uri.EscapeDataString(appKey)}", + $"timestamp={timestamp}", + $"sign={Uri.EscapeDataString(sign)}"); + return $"{root}?{query}"; + } } diff --git a/src/Oms.Tests/SmallBagTrackSignHelperTests.cs b/src/Oms.Tests/SmallBagTrackSignHelperTests.cs index e376bb9..e29b415 100644 --- a/src/Oms.Tests/SmallBagTrackSignHelperTests.cs +++ b/src/Oms.Tests/SmallBagTrackSignHelperTests.cs @@ -5,15 +5,19 @@ namespace Oms.Tests; public class SmallBagTrackSignHelperTests { - [Fact] - public void GenerateSign_ShouldMatchMd5UppercaseRule() + private const string AppKey = "W2606001"; + private const string AppSecret = "ziK8UzlQ39m3kdL346XEBg8nljY8os52"; + + [Theory] + [InlineData(1700000000, "A744D764DEE7C925AD6750257A4DEEE7")] + [InlineData(1782705351, "6C76CF34DFB53A0686C7C97284133BB7")] + [InlineData(1782705489, "E9633AB5419AB15D95D38903E8001E2D")] + public void GenerateSign_ShouldMatchOfficialMd5UppercaseRule(long timestamp, string expected) { - const string appKey = "W2606001"; - const long timestamp = 1700000000; - const string appSecret = "ziK8UzlQ39m3kdL346XEBg8nljY8os52"; + var signString = $"appKey={AppKey}×tamp={timestamp}&appSecret={AppSecret}"; - var sign = SmallBagTrackSignHelper.GenerateSign(appKey, timestamp, appSecret); - - sign.Should().Be("A744D764DEE7C925AD6750257A4DEEE7"); + SmallBagTrackSignHelper.GenerateSign(AppKey, timestamp, AppSecret) + .Should() + .Be(expected, because: $"signString={signString}"); } }