From 19669769441f45f33c4f7d37e08602547d78a42f Mon Sep 17 00:00:00 2001 From: xiaolipro <2357729423@qq.com> Date: Sat, 29 Nov 2025 17:23:19 +0800 Subject: [PATCH] t1 --- src/TrackCenter.Api/TrackCenterApiModule.cs | 7 +- src/TrackCenter.Api/appsettings.json | 4 + .../Dtos/ProvideServices/V2/OrderDtos.cs | 189 +++++++++++++++ src/TrackCenter.Application/Filters/README.md | 145 ++++++++++++ .../Filters/SignatureValidationFilter.cs | 139 +++++++++++ .../Filters/SignatureValidationOptions.cs | 17 ++ .../Filters/VerifySignatureAttribute.cs | 7 + .../Helpers/SignatureHelper.Example.cs | 98 ++++++++ .../Helpers/SignatureHelper.cs | 74 ++++++ .../Services/ApplicationServiceBase.cs | 3 + .../ProvideServices/OrderService.Link.cs | 6 +- .../ProvideServices/OrderServiceV2.cs | 224 ++++++++---------- 12 files changed, 780 insertions(+), 133 deletions(-) create mode 100644 src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs create mode 100644 src/TrackCenter.Application/Filters/README.md create mode 100644 src/TrackCenter.Application/Filters/SignatureValidationFilter.cs create mode 100644 src/TrackCenter.Application/Filters/SignatureValidationOptions.cs create mode 100644 src/TrackCenter.Application/Filters/VerifySignatureAttribute.cs create mode 100644 src/TrackCenter.Application/Helpers/SignatureHelper.Example.cs create mode 100644 src/TrackCenter.Application/Helpers/SignatureHelper.cs diff --git a/src/TrackCenter.Api/TrackCenterApiModule.cs b/src/TrackCenter.Api/TrackCenterApiModule.cs index a0817819..62ca6ea5 100644 --- a/src/TrackCenter.Api/TrackCenterApiModule.cs +++ b/src/TrackCenter.Api/TrackCenterApiModule.cs @@ -15,7 +15,7 @@ using TrackCenter.Domain.Extensions; using TrackCenter.Repository; using TrackCenter.Repository.Otel; using TrackCenter.Repository.Otel.Options; -using Microsoft.Extensions.DependencyInjection; +using TrackCenter.Application.Filters; namespace TrackCenter.Api; @@ -37,6 +37,9 @@ public class TrackCenterApiModule : FakeModule options.ScanApplicationServices(); }); + // 配置签名验证选项 + services.Configure(configuration.GetSection("SignatureValidation")); + // 全局授权配置:所有接口默认需要授权 services.AddMvc(options => { @@ -44,6 +47,8 @@ public class TrackCenterApiModule : FakeModule .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); + // 添加签名验证过滤器 + options.Filters.Add(); }).AddNewtonsoftJson(); services.AddFakeExceptionFilter() diff --git a/src/TrackCenter.Api/appsettings.json b/src/TrackCenter.Api/appsettings.json index e66bb3c3..24c5f9ef 100644 --- a/src/TrackCenter.Api/appsettings.json +++ b/src/TrackCenter.Api/appsettings.json @@ -187,5 +187,9 @@ "ExpiredTime": 50000, // 过期时间,long 类型,单位分钟,默认20分钟 "ClockSkew": 10, // 过期时间容错值,long 类型,单位秒,默认5秒 "Algorithm": "HS256" // 加密算法,string 类型,默认 HS256 + }, + "SignatureValidation": { + "Enabled": true, // 是否启用签名验证 + "MaxTimestampOffset": 300 // 时间戳允许的最大偏差(秒),默认300秒(5分钟) } } diff --git a/src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs b/src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs new file mode 100644 index 00000000..edf9b3aa --- /dev/null +++ b/src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs @@ -0,0 +1,189 @@ +using System.ComponentModel.DataAnnotations; + +namespace TrackCenter.Application.Dtos.ProvideServices.V2; + +public class UpsertSmallBagInput +{ + /// + /// 客户名称,或者code,必填 + /// + [Required] + public string Client { get; set; } + + /// + /// 客户单号,必填 + /// + [Required] + public string OrderNo { get; set; } + + /// + /// 尾程单号,必填 + /// + [Required] + public string TrackNo { get; set; } + + /// + /// 其他单号 + /// + public string OtherNo { get; set; } + + /// + /// 渠道名称,或者code + /// + public string Channel { get; set; } + + /// + /// 产品名称,或者code + /// + public string Product { get; set; } + + /// + /// 服务商code + /// + public string ApiCode { get; set; } + + /// + /// 收件人信息 + /// + public Consignee Consignee { get; set; } +} + +public class Consignee +{ + /// + /// 收件人国家,必填 + /// + [Required] + public string CountryCode { get; set; } + + /// + /// 收件人州省 + /// + public string Province { get; set; } + + /// + /// 收件人邮编 + /// + public string Postcode { get; set; } + + /// + /// 收件人城市 + /// + public string City { get; set; } + + /// + /// 收件人邮箱 + /// + public string Email { get; set; } +} + + +public class UpsertBigBagInput +{ + /// + /// 客户名称,或者code,必填 + /// + [Required] + public string Client { get; set; } + + /// + /// 大包号,必填 + /// + [Required] + public string BagNo { get; set; } + + /// + /// 其他单号 + /// + public string OtherNo { get; set; } + + /// + /// 渠道名称,或者code + /// + public string Channel { get; set; } + + /// + /// 产品名称,或者code + /// + public string Product { get; set; } + + /// + /// 产品名称,或者code + /// + public string Country { get; set; } +} + +public class UpsertMasterOrderInput +{ + /// + /// 服务商code + /// + public string ApiCode { get; set; } + + /// + /// 客户名称,或者code,必填 + /// + [Required] + public string Client { get; set; } + + /// + /// 主单号,必填 + /// + [Required] + public string MasterNo { get; set; } + + /// + /// 是否中转,必填 + /// + [Required] + public bool IsTransfer { get; set; } + + /// + /// 目的国家二字码,必填 + /// + [Required] + public string ArrivalCountry { get; set; } + + /// + /// 起运国家二字码,必填 + /// + [Required] + public string LeaveCountry { get; set; } + + /// + /// 航班信息(传当前订单的所有航班信息) + /// + public List FlightInfos { get; set; } +} + +/// +/// 航班信息 +/// +public class FlightInfo +{ + /// + /// 航班号 + /// + public string FlightNo { get; set; } + + /// + /// 起运港口三字码 + /// + public string LeavePort { get; set; } + + /// + /// 目的港口三字码 + /// + public string ArrivalPort { get; set; } + + /// + /// 预计到达时间 + /// + public string Eta { get; set; } + + /// + /// 预计起飞时间 + /// + public string Etd { get; set; } +} + diff --git a/src/TrackCenter.Application/Filters/README.md b/src/TrackCenter.Application/Filters/README.md new file mode 100644 index 00000000..67ea5c8f --- /dev/null +++ b/src/TrackCenter.Application/Filters/README.md @@ -0,0 +1,145 @@ +# 签名验证Filter使用说明 + +## 概述 + +签名验证Filter用于验证API请求的签名,确保请求的合法性和安全性。 + +## 配置 + +在 `appsettings.json` 中配置签名验证选项: + +```json +"SignatureValidation": { + "Enabled": true, // 是否启用签名验证 + "MaxTimestampOffset": 300, // 时间戳允许的最大偏差(秒),默认300秒(5分钟) + "AppSecrets": { + "test123": "abcdefg", // AppKey与AppSecret的映射 + "yourAppKey": "yourAppSecret" // 可以添加多个 + } +} +``` + +## 使用方法 + +### 1. 标记需要签名验证的Controller或Action + +在Controller类或Action方法上添加 `[RequireSignature]` 特性: + +```csharp +using TrackCenter.Api.Filters; + +// 整个Controller需要签名验证 +[RequireSignature] +public class YourController : Controller +{ + public IActionResult YourAction() + { + // ... + } +} + +// 或者只对特定Action进行签名验证 +public class YourController : Controller +{ + [RequireSignature] + public IActionResult YourAction() + { + // ... + } +} +``` + +### 2. 客户端请求 + +客户端需要在HTTP Header中传递以下公共参数: + +- **appKey** (String, 必填): 应用唯一标识,由平台分配 +- **timestamp** (Long, 必填): Unix时间戳(秒),与服务器时间差不能超过5分钟 +- **sign** (String, 必填): 签名结果 + +## 签名生成规则 + +### 步骤 + +1. **排序参数**: 按参数名排序:`appKey`, `timestamp` +2. **拼接字符串**: `appKey={value}timestamp={value}` +3. **拼接密钥**: `appKey={value}timestamp={value}&appSecret={value}` +4. **计算MD5**: 对拼接后的字符串计算MD5,并转为大写 + +### 示例 + +假设: +- appKey = `test123` +- timestamp = `1620000000` +- appSecret = `abcdefg` + +生成步骤: + +1. 排序后参数:`appKey`, `timestamp` +2. 拼接字符串:`appKey=test123timestamp=1620000000` +3. 拼接密钥:`appKey=test123timestamp=1620000000&appSecret=abcdefg` +4. 计算MD5并大写:`A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6` + +### C# 客户端示例代码 + +```csharp +using System; +using System.Security.Cryptography; +using System.Text; + +public class SignatureHelper +{ + public static string GenerateSignature(string appKey, long timestamp, string appSecret) + { + // 按照参数名排序后拼接 + var paramStr = $"appKey={appKey}timestamp={timestamp}"; + + // 拼接密钥 + var signStr = $"{paramStr}&appSecret={appSecret}"; + + // 计算MD5并转大写 + using var md5 = MD5.Create(); + var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(signStr)); + var signature = BitConverter.ToString(hashBytes).Replace("-", "").ToUpper(); + + return signature; + } +} + +// 使用示例 +var appKey = "test123"; +var appSecret = "abcdefg"; +var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); +var sign = SignatureHelper.GenerateSignature(appKey, timestamp, appSecret); + +// 在HTTP请求中添加Header +httpClient.DefaultRequestHeaders.Add("appKey", appKey); +httpClient.DefaultRequestHeaders.Add("timestamp", timestamp.ToString()); +httpClient.DefaultRequestHeaders.Add("sign", sign); +``` + +## 错误响应 + +当签名验证失败时,API会返回以下格式的错误响应(状态码401): + +```json +{ + "code": 401, + "message": "错误描述" +} +``` + +可能的错误信息: +- `缺少appKey参数` +- `缺少或格式错误的timestamp参数` +- `缺少sign参数` +- `时间戳超出允许范围,请检查系统时间` +- `无效的appKey` +- `签名验证失败` + +## 注意事项 + +1. **时间同步**: 确保客户端和服务器的时间同步,时间差不能超过5分钟 +2. **HTTPS**: 建议在生产环境中使用HTTPS,防止中间人攻击 +3. **AppSecret保密**: AppSecret必须妥善保管,不能泄露给第三方 +4. **每次请求生成新签名**: 不要重复使用旧的签名,每次请求都应该使用当前时间戳生成新的签名 diff --git a/src/TrackCenter.Application/Filters/SignatureValidationFilter.cs b/src/TrackCenter.Application/Filters/SignatureValidationFilter.cs new file mode 100644 index 00000000..bc417bde --- /dev/null +++ b/src/TrackCenter.Application/Filters/SignatureValidationFilter.cs @@ -0,0 +1,139 @@ +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using TrackCenter.Domain.VEntities; + +namespace TrackCenter.Application.Filters; + +/// +/// 签名验证过滤器 +/// +public class SignatureValidationFilter( + IOptions options, + ILogger logger, + IBasicRepository appRepository) + : IAsyncActionFilter +{ + private readonly SignatureValidationOptions _options = options.Value; + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + // 检查是否启用签名验证 + if (!_options.Enabled) + { + await next(); + return; + } + + // 检查是否标记为需要签名验证 + var requiresSignature = context.ActionDescriptor.EndpointMetadata + .Any(m => m.GetType() == typeof(VerifySignatureAttribute)); + + if (!requiresSignature) + { + await next(); + return; + } + + var request = context.HttpContext.Request; + + // 从Header中获取公共参数 + if (!request.Headers.TryGetValue("appKey", out var appKey) || string.IsNullOrWhiteSpace(appKey)) + { + context.Result = new JsonResult(new { code = 401, message = "缺少appKey参数" }) + { + StatusCode = 401 + }; + return; + } + + if (!request.Headers.TryGetValue("timestamp", out var timestampStr) || + !long.TryParse(timestampStr, out var timestamp)) + { + context.Result = new JsonResult(new { code = 401, message = "缺少或格式错误的timestamp参数" }) + { + StatusCode = 401 + }; + return; + } + + if (!request.Headers.TryGetValue("sign", out var sign) || string.IsNullOrWhiteSpace(sign)) + { + context.Result = new JsonResult(new { code = 401, message = "缺少sign参数" }) + { + StatusCode = 401 + }; + return; + } + + // 验证时间戳是否在允许的范围内(5分钟) + var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var timestampDiff = Math.Abs(currentTimestamp - timestamp); + + if (timestampDiff > _options.MaxTimestampOffset) + { + logger.LogWarning("时间戳验证失败,appKey: {AppKey}, timestamp: {Timestamp}, 当前时间戳: {CurrentTimestamp}, 差异: {Diff}秒", + appKey, timestamp, currentTimestamp, timestampDiff); + + context.Result = new JsonResult(new { code = 401, message = "时间戳超出允许范围,请检查系统时间" }) + { + StatusCode = 401 + }; + return; + } + + var appSecret = await appRepository.GetFirstAsync(x => x.AppKey == appKey, x => x.AppSecret); + if (appSecret.IsNullOrEmpty()) + { + logger.LogWarning("无效的appKey: {AppKey}", appKey); + context.Result = new JsonResult(new { code = 401, message = "无效的appKey" }) + { + StatusCode = 401 + }; + return; + } + + // 生成签名并验证 + var calculatedSign = GenerateSignature(appKey!, timestamp, appSecret!); + + if (!string.Equals(calculatedSign, sign, StringComparison.OrdinalIgnoreCase)) + { + logger.LogWarning("签名验证失败,appKey: {AppKey}, 期望签名: {Expected}, 实际签名: {Actual}", + appKey, calculatedSign, sign); + + context.Result = new JsonResult(new { code = 401, message = "签名验证失败" }) + { + StatusCode = 401 + }; + return; + } + + // 验证通过,继续执行 + await next(); + } + + /// + /// 生成签名 + /// 规则: + /// 1. 排序后参数:appKey,timestamp,appSecret + /// 2. 用&拼接得到字符串:appKey={value}×tamp={value}&appSecret={value} + /// 3. 计算MD5并大写,并去掉横杆- + /// + private string GenerateSignature(string appKey, long timestamp, string appSecret) + { + // 按照参数名排序后拼接 + var paramStr = $""; + + // 拼接密钥 + var signStr = $"appKey={appKey}×tamp={timestamp}&appSecret={appSecret}"; + + // 计算MD5并转大写 + using var md5 = MD5.Create(); + var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(signStr)); + var signature = BitConverter.ToString(hashBytes).Replace("-", "").ToUpper(); + + return signature; + } +} diff --git a/src/TrackCenter.Application/Filters/SignatureValidationOptions.cs b/src/TrackCenter.Application/Filters/SignatureValidationOptions.cs new file mode 100644 index 00000000..43af4b19 --- /dev/null +++ b/src/TrackCenter.Application/Filters/SignatureValidationOptions.cs @@ -0,0 +1,17 @@ +namespace TrackCenter.Application.Filters; + +/// +/// 签名验证配置选项 +/// +public class SignatureValidationOptions +{ + /// + /// 是否启用签名验证 + /// + public bool Enabled { get; set; } = true; + + /// + /// 时间戳允许的最大偏差(秒),默认300秒(5分钟) + /// + public int MaxTimestampOffset { get; set; } = 300; +} diff --git a/src/TrackCenter.Application/Filters/VerifySignatureAttribute.cs b/src/TrackCenter.Application/Filters/VerifySignatureAttribute.cs new file mode 100644 index 00000000..3787b447 --- /dev/null +++ b/src/TrackCenter.Application/Filters/VerifySignatureAttribute.cs @@ -0,0 +1,7 @@ +namespace TrackCenter.Application.Filters; + +/// +/// 标记接口需要进行签名验证 +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] +public class VerifySignatureAttribute : Attribute; diff --git a/src/TrackCenter.Application/Helpers/SignatureHelper.Example.cs b/src/TrackCenter.Application/Helpers/SignatureHelper.Example.cs new file mode 100644 index 00000000..814dde2c --- /dev/null +++ b/src/TrackCenter.Application/Helpers/SignatureHelper.Example.cs @@ -0,0 +1,98 @@ +/* + * SignatureHelper 使用示例 + * + * 根据API签名机制文档实现 + */ + +namespace TrackCenter.Application.Helpers; + +/// +/// SignatureHelper 使用示例 +/// +public static class SignatureHelperExample +{ + /// + /// 生成签名示例 + /// + public static void GenerateSignExample() + { + // 示例参数 + string appKey = "test123"; + long timestamp = 1620000000; // Unix时间戳(秒) + string appSecret = "abcdefg"; + + // 生成签名 + string sign = SignatureHelper.GenerateSign(appKey, timestamp, appSecret); + + // 输出: A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6 (示例结果) + Console.WriteLine($"生成的签名: {sign}"); + } + + /// + /// 验证签名示例 + /// + public static void VerifySignExample() + { + string appKey = "test123"; + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); // 当前时间戳 + string appSecret = "abcdefg"; + + // 生成签名 + string sign = SignatureHelper.GenerateSign(appKey, timestamp, appSecret); + + // 验证签名 + bool isValid = SignatureHelper.VerifySign(appKey, timestamp, sign, appSecret); + + Console.WriteLine($"签名验证结果: {isValid}"); // 输出: true + } + + /// + /// API请求示例 + /// + public static async Task ApiRequestExample() + { + // 1. 准备参数 + string appKey = "your_app_key"; + string appSecret = "your_app_secret"; + long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + + // 2. 生成签名 + string sign = SignatureHelper.GenerateSign(appKey, timestamp, appSecret); + + // 3. 构建请求 + using var httpClient = new HttpClient(); + var requestUrl = $"https://api.example.com/endpoint?appKey={appKey}×tamp={timestamp}&sign={sign}"; + + // 4. 发送请求 + var response = await httpClient.GetAsync(requestUrl); + var content = await response.Content.ReadAsStringAsync(); + + Console.WriteLine($"响应内容: {content}"); + } + + /// + /// API验证示例(服务端) + /// + public static bool ValidateApiRequest(string appKey, long timestamp, string sign) + { + // 从数据库或配置获取appSecret + string appSecret = GetAppSecretFromDatabase(appKey); + + // 验证签名 + // 注意:必须使用当前时间戳,且与服务器时间差不超过5分钟 + bool isValid = SignatureHelper.VerifySign(appKey, timestamp, sign, appSecret); + + if (!isValid) + { + throw new UnauthorizedAccessException("签名验证失败"); + } + + return true; + } + + private static string GetAppSecretFromDatabase(string appKey) + { + // 实际项目中应从数据库查询 + return "abcdefg"; + } +} diff --git a/src/TrackCenter.Application/Helpers/SignatureHelper.cs b/src/TrackCenter.Application/Helpers/SignatureHelper.cs new file mode 100644 index 00000000..08d116c5 --- /dev/null +++ b/src/TrackCenter.Application/Helpers/SignatureHelper.cs @@ -0,0 +1,74 @@ +using System.Security.Cryptography; +using System.Text; + +namespace TrackCenter.Application.Helpers; + +/// +/// API签名工具类 +/// +public static class SignatureHelper +{ + /// + /// 生成API签名 + /// + /// 应用唯一标识 + /// 时间戳(Unix时间戳,单位:秒) + /// 应用密钥 + /// MD5签名结果(大写) + public static string GenerateSign(string appKey, long timestamp, string appSecret) + { + // 1. 排序后拼接:appKey, timestamp + var sortedParams = $"appKey={appKey}×tamp={timestamp}"; + + // 2. 拼接密钥:添加appSecret + var signString = $"{sortedParams}&appSecret={appSecret}"; + + // 3. 计算MD5并转大写 + return CalculateMd5Hash(signString); + } + + /// + /// 验证签名 + /// + /// 应用唯一标识 + /// 时间戳 + /// 待验证的签名 + /// 应用密钥 + /// 允许的最大时间差(分钟),默认5分钟 + /// 签名是否有效 + public static bool VerifySign(string appKey, long timestamp, string sign, string appSecret, int maxTimeDifferenceMinutes = 5) + { + // 1. 验证时间戳(防止重放攻击) + var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var timeDifference = Math.Abs(currentTimestamp - timestamp); + + if (timeDifference > maxTimeDifferenceMinutes * 60) + { + return false; + } + + // 2. 生成签名并比较 + var expectedSign = GenerateSign(appKey, timestamp, appSecret); + return string.Equals(expectedSign, sign, StringComparison.OrdinalIgnoreCase); + } + + /// + /// 计算MD5哈希值(大写) + /// + /// 输入字符串 + /// MD5哈希值(大写) + private static string CalculateMd5Hash(string input) + { + using var md5 = MD5.Create(); + var inputBytes = Encoding.UTF8.GetBytes(input); + var hashBytes = md5.ComputeHash(inputBytes); + + var sb = new StringBuilder(); + foreach (var b in hashBytes) + { + sb.Append(b.ToString("X2")); + } + + return sb.ToString(); + } +} diff --git a/src/TrackCenter.Application/Services/ApplicationServiceBase.cs b/src/TrackCenter.Application/Services/ApplicationServiceBase.cs index f396ac71..9bc1c136 100644 --- a/src/TrackCenter.Application/Services/ApplicationServiceBase.cs +++ b/src/TrackCenter.Application/Services/ApplicationServiceBase.cs @@ -1,8 +1,11 @@ using System.Security.Claims; +using Microsoft.AspNetCore.Http; namespace TrackCenter.Application.Services; public class ApplicationServiceBase : ApplicationService { public int CurrentUserId => CurrentUser.GetId(); + + public string? AppKey => LazyServiceProvider.GetRequiredService().HttpContext?.Request.Headers["AppKey"]; } diff --git a/src/TrackCenter.Application/Services/ProvideServices/OrderService.Link.cs b/src/TrackCenter.Application/Services/ProvideServices/OrderService.Link.cs index 0dcd2e40..938bc44e 100644 --- a/src/TrackCenter.Application/Services/ProvideServices/OrderService.Link.cs +++ b/src/TrackCenter.Application/Services/ProvideServices/OrderService.Link.cs @@ -31,10 +31,10 @@ public partial class OrderService throw new BusinessException(message: $"订单:{input.OrderNo}不存在"); } - // tips:循环关联问题再消费时解决 - if (input.OrderType == input.LinkOrderType) + // tips:2025-11-29 zzh 订单类型(1是小包,2是大包,4是主单),必填,同时orderType小于linkOrderType + if (input.OrderType < input.LinkOrderType) { - throw new BusinessException(message: "不能同级关联"); + throw new BusinessException(message: "orderType小于linkOrderType"); } if (input.LinkOrderNos.Count < 1 || input.LinkOrderType == 0) return; diff --git a/src/TrackCenter.Application/Services/ProvideServices/OrderServiceV2.cs b/src/TrackCenter.Application/Services/ProvideServices/OrderServiceV2.cs index efb22b49..907ca399 100644 --- a/src/TrackCenter.Application/Services/ProvideServices/OrderServiceV2.cs +++ b/src/TrackCenter.Application/Services/ProvideServices/OrderServiceV2.cs @@ -1,12 +1,13 @@ -using System.ComponentModel.DataAnnotations; using InterfaceForward.Domain.Shared.Helpers; -using Microsoft.AspNetCore.Components.Forms; +using TrackCenter.Application.Dtos.ProvideServices.V2; +using TrackCenter.Application.Filters; using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Aggregates.TrackPullAggregate; using TrackCenter.Repository.Aggregates.OrderAggregate; namespace TrackCenter.Application.Services.ProvideServices; +[VerifySignature] public class OrderServiceV2( IOrderRepository orderRepository, IOrderOperationRecordEsRepository orderOperationRecordEsRepository, @@ -23,7 +24,7 @@ public class OrderServiceV2( { // 校验订单是否已存在,存在则更新 var order = await orderRepository.GetFirstAsync(x => - x.AppKey == input.ApiCode && x.OrderType == TrackOrderType.小包 && x.OrderNo == input.OrderNo); + x.AppKey == AppKey && x.OrderType == TrackOrderType.小包 && x.OrderNo == input.OrderNo); // 订单已存在则视为更新 if (order != null) { @@ -35,7 +36,7 @@ public class OrderServiceV2( { OrderId = order.Id, OrderNo = input.OrderNo, - OptionType = OrderOperationType.修改订单.ToString(), + OptionType = nameof(OrderOperationType.修改订单), Content = input.ToJson(), CreateUserId = CurrentUserId, CreateUserName = CurrentUser.UserName ?? string.Empty, @@ -82,7 +83,7 @@ public class OrderServiceV2( { // 校验订单是否已存在,存在则更新 var order = await orderRepository.GetFirstAsync(x => - x.AppKey == input.ApiCode && x.OrderType == TrackOrderType.大包 && x.OrderNo == input.BagNo); + x.AppKey == AppKey && x.OrderType == TrackOrderType.大包 && x.OrderNo == input.BagNo); // 订单已存在则视为更新 if (order != null) { @@ -131,11 +132,71 @@ public class OrderServiceV2( }; } + /// + /// 新增、更新主单(存在就更新) + /// + /// + /// + [HttpPost("OrderV2/UpsertMasterOrder")] + public async Task UpsertAsync(UpsertMasterOrderInput input) + { + // 校验订单是否已存在,存在则更新 + var order = await orderRepository.GetFirstAsync(x => + x.AppKey == AppKey && x.OrderType == TrackOrderType.主单 && x.OrderNo == input.MasterNo); + // 订单已存在则视为更新 + if (order != null) + { + order = await BuiltOrder(input); + + await orderRepository.UpdateOrderAsync(order); + + var index = new OrderOperationRecordIndex + { + OrderId = order.Id, + OrderNo = input.MasterNo, + OptionType = nameof(OrderOperationType.修改订单), + Content = input.ToJson(), + CreateUserId = CurrentUserId, + CreateUserName = CurrentUser.UserName ?? string.Empty, + }; + + _ = orderOperationRecordEsRepository.AddAsync(index); + + return new IdBaseDto + { + Id = order.Id + }; + } + + order = await BuiltOrder(input); + var orderId = await orderRepository.CreateOrderAsync(order); + + // 写入操作日志 + var indexForAdd = new OrderOperationRecordIndex + { + OrderId = orderId, + OrderNo = input.MasterNo, + OptionType = nameof(OrderOperationType.创建订单), + Content = input.ToJson(), + CreateUserId = CurrentUserId, + CreateUserName = CurrentUser.UserName ?? string.Empty, + CreateTime = DateTime.Now + }; + _ = orderOperationRecordEsRepository.AddAsync(indexForAdd); + + + return new IdBaseDto + { + Id = orderId + }; + } + private async Task BuiltOrder(UpsertSmallBagInput input) { var order = new OrderEntity { - AppKey = input.ApiCode, + AppKey = AppKey!, + ServiceProviderCode = input.ApiCode, OrderType = TrackOrderType.小包, OrderNo = input.OrderNo, TrackNo = input.TrackNo, @@ -166,8 +227,8 @@ public class OrderServiceV2( { var order = new OrderEntity { - AppKey = input.ApiCode, - ServiceProviderCode = input.ApiCode, + AppKey = AppKey!, + //ServiceProviderCode = input.ApiCode, OrderType = TrackOrderType.大包, OrderNo = input.BagNo, ThirdPartyOrderNumber = input.OtherNo, @@ -190,6 +251,32 @@ public class OrderServiceV2( return Task.FromResult(order); } + private async Task BuiltOrder(UpsertMasterOrderInput input) + { + var order = new OrderEntity + { + AppKey = AppKey!, + ServiceProviderCode = input.ApiCode, + OrderType = TrackOrderType.小包, + OrderNo = input.MasterNo, + IsTransit = input.IsTransfer, + ArrivalCountry = input.ArrivalCountry, + LeaveCountry = input.LeaveCountry, + }; + + order.OrderFlights = new List(); + + var config = await trackPullConfigRepository + .FirstOrDefaultAsync(x => x.AppKey == input.ApiCode + && x.ServiceProviderCode == input.ApiCode); + + if (config == null) throw new BusinessException(message: "没有一个拉取配置可以适用,请配置,否则无法拉取轨迹"); + + order.OrderPullStatus = [CreateOrderPullStatus(config, order)]; + + return order; + } + private static OrderPullStatusEntity CreateOrderPullStatus(TrackPullConfigEntity config, OrderEntity order) { var status = new OrderPullStatusEntity @@ -219,124 +306,3 @@ public class OrderServiceV2( return status; } } - -public class UpsertSmallBagInput -{ - /// - /// 客户名称,或者code,必填 - /// - [Required] - public string Client { get; set; } - - /// - /// 客户单号,必填 - /// - [Required] - public string OrderNo { get; set; } - - /// - /// 尾程单号,必填 - /// - [Required] - public string TrackNo { get; set; } - - /// - /// 其他单号 - /// - public string OtherNo { get; set; } - - /// - /// 渠道名称,或者code - /// - public string Channel { get; set; } - - /// - /// 产品名称,或者code - /// - public string Product { get; set; } - - /// - /// 服务商code - /// - public string ApiCode { get; set; } - - /// - /// 收件人信息 - /// - public Consignee Consignee { get; set; } -} - -public class Consignee -{ - /// - /// 收件人国家,必填 - /// - [Required] - public string CountryCode { get; set; } - - /// - /// 收件人州省 - /// - public string Province { get; set; } - - /// - /// 收件人邮编 - /// - public string Postcode { get; set; } - - /// - /// 收件人城市 - /// - public string City { get; set; } - - /// - /// 收件人邮箱 - /// - public string Email { get; set; } -} - -public class UpsertBigBagInput -{ - /// - /// 客户名称,或者code,必填 - /// - [Required] - public string Client { get; set; } - - /// - /// 客户单号,必填 - /// - [Required] - public string BagNo { get; set; } - - /// - /// 其他单号 - /// - public string OtherNo { get; set; } - - /// - /// 渠道名称,或者code - /// - public string Channel { get; set; } - - /// - /// 产品名称,或者code - /// - public string Product { get; set; } - - /// - /// 产品名称,或者code - /// - public string Country { get; set; } - - /// - /// 服务商code - /// - public string ApiCode { get; set; } -} - -public class UpsertMsterOrderInput -{ - -} -