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={}×tamp={}&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}");
}
}