diff --git a/Directory.Build.props b/Directory.Build.props index e7da47d3..7d6f04d0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 8.0.5.16 + 8.0.5.17 diff --git a/scripts/sql/migrate_track_pull_config_order_type.sql b/scripts/sql/migrate_track_pull_config_order_type.sql new file mode 100644 index 00000000..a7894259 --- /dev/null +++ b/scripts/sql/migrate_track_pull_config_order_type.sql @@ -0,0 +1,6 @@ +-- 轨迹拉取配置增加订单类型(小包/大包/主单),用于同一应用+服务商下按订单类型区分配置 +-- 库:TrackCenter(t_track_pull_config) +-- MySQL;若 OrderType 列已存在可跳过 + +ALTER TABLE t_track_pull_config + ADD COLUMN OrderType INT NOT NULL DEFAULT 1 COMMENT '订单类型(TrackOrderType:1小包 2大包 4主单)' AFTER ServiceProviderCode; diff --git a/src/TrackCenter.Api/Program.cs b/src/TrackCenter.Api/Program.cs index f63d2ca0..ba0d1d66 100644 --- a/src/TrackCenter.Api/Program.cs +++ b/src/TrackCenter.Api/Program.cs @@ -2,8 +2,9 @@ using Serilog; using TrackCenter.Api; var builder = WebApplication.CreateSlimBuilder(args); -builder.Configuration.AddConsul("idwms/common", source => source.ReloadOnChange = true); -builder.Configuration.AddConsul("idwms/trackapi", source => source.ReloadOnChange = true); +var prefix = builder.Environment.EnvironmentName == "Development" ? "dev" : "idwms"; +builder.Configuration.AddConsul($"{prefix}/common", source => source.ReloadOnChange = true); +builder.Configuration.AddConsul($"{prefix}/trackapi", source => source.ReloadOnChange = true); var configuration = builder.Configuration; Log.Logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) diff --git a/src/TrackCenter.Api/TrackCenterApiModule.cs b/src/TrackCenter.Api/TrackCenterApiModule.cs index 511fc8a3..4681cf88 100644 --- a/src/TrackCenter.Api/TrackCenterApiModule.cs +++ b/src/TrackCenter.Api/TrackCenterApiModule.cs @@ -2,7 +2,6 @@ using Fake.AspNetCore; using Fake.AspNetCore.Auditing; using Fake.AspNetCore.Authentication; using Fake.AspNetCore.ExceptionHandling; -using Fake.AspNetCore; using Fake.AspNetCore.Newtonsoft; using Fake.AspNetCore.Swagger; using Fake.Autofac; @@ -15,7 +14,6 @@ using FluentValidation.AspNetCore; using Hangfire; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.HttpOverrides; -using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Authorization; using OpenTelemetry.Resources; using OpenTelemetry.Trace; @@ -182,8 +180,8 @@ public class TrackCenterApiModule : FakeModule { DisplayStorageConnectionString = false, IgnoreAntiforgeryToken = true, + // 「返回站点」链接;PathBase 已由 UsePathBase 处理,勿再设 PrefixPath,否则会变成 /track/track/hangfire/... AppPath = configuration["ASPNETCORE_PATHBASE"], - PrefixPath = configuration["ASPNETCORE_PATHBASE"], DashboardTitle = "轨迹预警任务", Authorization = [new AnonymousHangfireAuthorizationFilter()] }); diff --git a/src/TrackCenter.Api/appsettings.Development.json b/src/TrackCenter.Api/appsettings.Development.json deleted file mode 100644 index 01257b2b..00000000 --- a/src/TrackCenter.Api/appsettings.Development.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "Microsoft.AspNetCore": "Warning" - } - }, - "FeiShuNotice": { - "EnableFeiShuExceptionSubscribe": false - } -} diff --git a/src/TrackCenter.Api/appsettings.json b/src/TrackCenter.Api/appsettings.json index 860fa469..06ba18d7 100644 --- a/src/TrackCenter.Api/appsettings.json +++ b/src/TrackCenter.Api/appsettings.json @@ -1,7 +1,7 @@ { "UseLocalConfigs": false, "Consul": { - "Address": "https://consul.nanafa.com", - "Token": "97217125-df0b-5c0a-dbfc-8885def5e471" + "Address": "http://test.ideship.com:30850", + "Token": "c0aea902-dca0-ac29-299b-a0d47e5402aa" } } diff --git a/src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs b/src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs index edf9b3aa..8256c0d4 100644 --- a/src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs +++ b/src/TrackCenter.Application/Dtos/ProvideServices/V2/OrderDtos.cs @@ -80,6 +80,11 @@ public class Consignee public class UpsertBigBagInput { + /// + /// 服务商code + /// + public string ApiCode { get; set; } + /// /// 客户名称,或者code,必填 /// @@ -108,7 +113,7 @@ public class UpsertBigBagInput public string Product { get; set; } /// - /// 产品名称,或者code + /// 目的国家二字码 /// public string Country { get; set; } } diff --git a/src/TrackCenter.Application/Helpers/OSS/OSSHelper.cs b/src/TrackCenter.Application/Helpers/OSS/OSSHelper.cs deleted file mode 100644 index be87c471..00000000 --- a/src/TrackCenter.Application/Helpers/OSS/OSSHelper.cs +++ /dev/null @@ -1,263 +0,0 @@ -using System.Security.Cryptography; -using Aliyun.OSS; - -namespace TrackCenter.Application.Helpers.OSS -{ - /// - /// 阿里云OSS帮助类 - /// - public static class OSSHelper - { - #region 初始化 - private static OSSOptions _OSSOptions; - internal static void Init(OSSOptions OSSOptions) - { - _OSSOptions = OSSOptions; - } - #endregion - - #region 获取client - private static OssClient GetClient() - { - string errorMessage = null; - if (string.IsNullOrEmpty(_OSSOptions?.Endpoint)) - { - errorMessage += $"未找到核心配置参数【{nameof(_OSSOptions.Endpoint)}】!"; - } - - if (string.IsNullOrEmpty(_OSSOptions?.AccessKeyId)) - { - errorMessage += $"未找到核心配置参数【{nameof(_OSSOptions.AccessKeyId)}】!"; - } - if (string.IsNullOrEmpty(_OSSOptions?.AccessKeySecret)) - { - errorMessage += $"未找到核心配置参数【{nameof(_OSSOptions.AccessKeySecret)}】!"; - } - - if (!string.IsNullOrEmpty(errorMessage)) - { - throw new Exception(errorMessage); - } - var client = new OssClient(_OSSOptions.Endpoint, _OSSOptions.AccessKeyId, _OSSOptions.AccessKeySecret); - return client; - } - #endregion - - #region 上传 - - /// - /// 上传文件 - /// - /// 文档流 - /// 文件名称 - /// 是否使用自定义ossKey,如果为true,会以fileName为key - /// - public static UploadResultDto Upload(Stream data, string fileName = "", bool usCustomKey = false, bool isPublicBucket = false) - { - if (data == null || data.Length == 0) - return new UploadResultDto(false); - - var result = Private_Upload(fileName, data, usCustomKey, isPublicBucket); - return result; - } - - private static UploadResultDto Private_Upload(string fileName, Stream data, bool usCustomKey = false, bool isPublicBucket = false) - { - var client = GetClient(); - - var ossKey = ""; - if (usCustomKey) - { - ossKey = fileName; - } - else - { - var md5 = ComputeDataMd5(data).ToUpper();// 计算文件MD5值 - var ext = Path.GetExtension(fileName); - ossKey = $"{md5}{ext}"; - } - - - // 文件已经存在直接返回 - var existingObject = client.DoesObjectExist(_OSSOptions.BucketName, ossKey); - if (existingObject) - { - var res = new UploadResultDto(true); - res.TempUrl = ossKey.GetUrlByKey(isPublicBucket); - res.Key = ossKey; - res.FileName = fileName; - - return res; - } - - // 上传文件 - bool putResult = false; - - using (var pubObj = client.PutObject(_OSSOptions.BucketName, ossKey, data)) - { - putResult = pubObj.HttpStatusCode == System.Net.HttpStatusCode.OK; - } - - - if (putResult)//上传成功 - { - var result = new UploadResultDto(true); - result.FileName = fileName; - result.TempUrl = ossKey.GetUrlByKey(isPublicBucket); - result.Key = ossKey; - return result; - } - else - { - // 上传失败 - var result = new UploadResultDto(false); - result.FileName = fileName; - return result; - } - } - public static string ComputeDataMd5(Stream data) - { - using (var md5 = MD5.Create()) - { - var hash = BitConverter.ToString(md5.ComputeHash(data)).Replace("-", "").Replace("=", "").ToLower(); - data.Position = 0; - return hash; - } - } - #endregion - - #region 下载 - - /// - /// 通过Key获取文件的URL - 同步 - /// - /// Key - /// 下载Url - public static string GetUrlByKey(this string key, bool isPublicBucket = false) - { - if (string.IsNullOrEmpty(key)) - { - throw new Exception($"指定的key不能为空"); - } - - try - { - var client = GetClient(); - Uri url = client.GeneratePresignedUri(_OSSOptions.BucketName, key); - - if (isPublicBucket) - { - var urlStr = url?.GetLeftPart(UriPartial.Path); - return (_OSSOptions.ReturnHttpsUrl && urlStr?.ToString()?.StartsWith("http://") == true) ? "https://" + urlStr.ToString().Substring(7) : urlStr.ToString(); - } - - - return (_OSSOptions.ReturnHttpsUrl && url?.ToString()?.StartsWith("http://") == true) ? "https://" + url.ToString().Substring(7) : url.ToString(); - } - catch (Exception ex) - { - throw new Exception(ex.Message); - } - } - - - - /// - /// 根据一组key返回url - /// - /// - /// - /// - public static Dictionary GetUrlByKeys(params string[] keys) - { - var resDic = new Dictionary(); - if (keys == null || keys.Count() <= 0) - return resDic; - - - var returnHttps = _OSSOptions.ReturnHttpsUrl; - var client = GetClient(); - - foreach (var item in keys) - { - if (resDic.ContainsKey(item)) - continue; - - Uri url = client.GeneratePresignedUri(_OSSOptions.BucketName, item); - if (url == null) - continue; - - resDic.Add(item, returnHttps ? url.ToString().Replace("http", "https") : url.ToString()); - } - return resDic; - } - - - /// - /// 通过Key获取文件流 - /// - /// Key - /// - public static Stream GetStreamByKey(this string key) - { - if (string.IsNullOrEmpty(key)) - { - throw new Exception($"指定的key不能为空"); - } - - - try - { - var client = GetClient(); - var obj = client.GetObject(_OSSOptions.BucketName, key); - if (obj != null) - { - return obj.Content; - } - else - { - throw new Exception($"文件获取异常"); - } - } - catch (Exception ex) - { - throw new Exception(ex.Message); - } - } - #endregion - - #region 删除 - /// - /// 删除文件 - /// - /// - /// - public static DeleteObjectsResult DeleteObjectsByKeys(params string[] keys) - { - if (keys == null || keys.Length <= 0) - return default(DeleteObjectsResult); - - var client = GetClient(); - var request = new DeleteObjectsRequest(_OSSOptions.BucketName, keys); - - return client.DeleteObjects(request); - } - #endregion - - #region 查看数据信息 - /// - /// 查看文档信息 - /// - /// - /// - public static ObjectMetadata GetObjectMetadata(string ossKey) - { - var client = GetClient().GetObjectMetadata(_OSSOptions.BucketName, ossKey); - return client; - } - #endregion - - - - } -} diff --git a/src/TrackCenter.Application/Helpers/OSS/OSSOptions.cs b/src/TrackCenter.Application/Helpers/OSS/OSSOptions.cs deleted file mode 100644 index a2ecd307..00000000 --- a/src/TrackCenter.Application/Helpers/OSS/OSSOptions.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace TrackCenter.Application.Helpers.OSS; - -public class OSSOptions -{ - - /// - /// 秘钥Id(类似于公钥) - /// - public string AccessKeyId { get; set; } - - /// - /// 访问秘钥 - /// - public string AccessKeySecret { get; set; } - - /// - /// OSS端点 - /// - public string Endpoint { get; set; } - - /// - /// 存储空间名称 - /// 注意,如果有多个BucketName的话,可以直接使用代码的方式设置这个值 - /// - public string BucketName { get; set; } - - /// - /// 返回https的url,默认false返回http - /// 阿里OSS默认支持http,https,不需要额外配置 - /// - public bool ReturnHttpsUrl { get; set; } -} diff --git a/src/TrackCenter.Application/Helpers/OSS/ObjectStorageUrlHelper.cs b/src/TrackCenter.Application/Helpers/OSS/ObjectStorageUrlHelper.cs new file mode 100644 index 00000000..931b20c4 --- /dev/null +++ b/src/TrackCenter.Application/Helpers/OSS/ObjectStorageUrlHelper.cs @@ -0,0 +1,53 @@ +using Fake.ObjectStorage; + +namespace TrackCenter.Application.Helpers.OSS; + +/// +/// 轨迹附件 object key → 可访问 URL(Fake.ObjectStorage / TencentCos)。 +/// +public static class ObjectStorageUrlHelper +{ + public static async Task> GetUrlByKeysAsync( + this IObjectStorage storage, + IEnumerable keys, + CancellationToken cancellationToken = default) + { + var result = new Dictionary(StringComparer.Ordinal); + foreach (var raw in keys + .Where(k => !string.IsNullOrWhiteSpace(k)) + .Select(k => k!.Trim()) + .Distinct(StringComparer.Ordinal)) + { + if (raw.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + || raw.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + result[raw] = raw; + continue; + } + + result[raw] = await storage.GetUrlAsync(raw, cancellationToken: cancellationToken); + } + + return result; + } + + public static async Task GetUrlByKeyAsync( + this IObjectStorage storage, + string? key, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(key)) + { + return string.Empty; + } + + var raw = key.Trim(); + if (raw.StartsWith("http://", StringComparison.OrdinalIgnoreCase) + || raw.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + return raw; + } + + return await storage.GetUrlAsync(raw, cancellationToken: cancellationToken); + } +} diff --git a/src/TrackCenter.Application/Helpers/OSS/UploadResultDto.cs b/src/TrackCenter.Application/Helpers/OSS/UploadResultDto.cs deleted file mode 100644 index 9359d503..00000000 --- a/src/TrackCenter.Application/Helpers/OSS/UploadResultDto.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace TrackCenter.Application.Helpers.OSS; - -public class UploadResultDto -{ - /// - /// 标识本次操作是否成功 - /// - public bool IsSuccess { get; set; } - - /// - /// 临时URL,过期时间15min - /// - public string TempUrl { get; set; } - - /// - /// 文件唯一标识Key,通常保存在数据 - /// 后期所有的文件操作都是通过这个Key - /// - public string Key { get; set; } - - /// - /// 文件原始名称 - /// - public string FileName { get; set; } - - public UploadResultDto(bool isSuccess) - { - IsSuccess = isSuccess; - } -} diff --git a/src/TrackCenter.Application/Kafka/WarningCalcServiceExtension.cs b/src/TrackCenter.Application/Kafka/WarningCalcServiceExtension.cs index 91b0d5b2..bca1725a 100644 --- a/src/TrackCenter.Application/Kafka/WarningCalcServiceExtension.cs +++ b/src/TrackCenter.Application/Kafka/WarningCalcServiceExtension.cs @@ -34,30 +34,32 @@ public static class WarningCalcServiceExtension services.AddSingleton>(sp => { DebounceHelper.DebounceWrapper? reportFuncWrapper = null; - var bb = new ConsumerBuilder( - new ConsumerConfig - { - ClientId = Dns.GetHostName(), - GroupId = groupId, - BootstrapServers = conf.GetValue("Kafka:BootstrapServers"), - AutoOffsetReset = AutoOffsetReset.Earliest, - //偏移提交控制 - EnablePartitionEof = false, - EnableAutoOffsetStore = false, - EnableAutoCommit = false, - //链接控制 - SessionTimeoutMs = conf.GetValue("Kafka:Consumer:SessionTimeoutMs"), - MaxPollIntervalMs = conf.GetValue("Kafka:Consumer:MaxPollIntervalMs"), - HeartbeatIntervalMs = conf.GetValue("Kafka:Consumer:HeartbeatIntervalMs"), - //吞吐量 - FetchMinBytes = conf.GetValue("Kafka:Consumer:FetchMinBytes"), - FetchMaxBytes = conf.GetValue("Kafka:Consumer:FetchMaxBytes"), - FetchWaitMaxMs = conf.GetValue("Kafka:Consumer:FetchWaitMaxMs"), - CancellationDelayMaxMs = 100, - // Debug = "all", - // LogConnectionClose = false, - // LogQueue = true - }) + var consumerConfig = new ConsumerConfig + { + ClientId = Dns.GetHostName(), + GroupId = groupId, + BootstrapServers = conf.GetValue("Kafka:BootstrapServers"), + AutoOffsetReset = AutoOffsetReset.Earliest, + //偏移提交控制 + EnablePartitionEof = false, + EnableAutoOffsetStore = false, + EnableAutoCommit = false, + //链接控制 + SessionTimeoutMs = conf.GetValue("Kafka:Consumer:SessionTimeoutMs"), + MaxPollIntervalMs = conf.GetValue("Kafka:Consumer:MaxPollIntervalMs"), + HeartbeatIntervalMs = conf.GetValue("Kafka:Consumer:HeartbeatIntervalMs"), + //吞吐量 + FetchMinBytes = conf.GetValue("Kafka:Consumer:FetchMinBytes"), + FetchMaxBytes = conf.GetValue("Kafka:Consumer:FetchMaxBytes"), + FetchWaitMaxMs = conf.GetValue("Kafka:Consumer:FetchWaitMaxMs"), + CancellationDelayMaxMs = 100, + // Debug = "all", + // LogConnectionClose = false, + // LogQueue = true + }; + ApplyOptionalSasl(consumerConfig, conf); + + var bb = new ConsumerBuilder(consumerConfig) .SetKeyDeserializer(sp.GetRequiredService>()) .SetValueDeserializer(sp.GetRequiredService>()) .SetErrorHandler((_, error) => @@ -93,31 +95,34 @@ public static class WarningCalcServiceExtension services.AddSingleton>(sp => { DebounceHelper.DebounceWrapper? reportFuncWrapper = null; - var bb = new ProducerBuilder(new ProducerConfig() - { - ClientId = Dns.GetHostName(), - BootstrapServers = conf.GetValue("Kafka:BootstrapServers"), - // 确保消息发送可靠 - Acks = Acks.All, - EnableIdempotence = true, - MessageSendMaxRetries = conf.GetValue("Kafka:Producer:MessageSendMaxRetries"), - RequestTimeoutMs = conf.GetValue("Kafka:Producer:RequestTimeoutMs"), - // 消息体大小 - CompressionType = conf.GetValue("Kafka:Producer:CompressionType"), - CompressionLevel = conf.GetValue("Kafka:Producer:CompressionLevel"), - // 消息发送速率控制(任意一条件先达到就发送) - LingerMs = conf.GetValue("Kafka:Producer:LingerMs"), - BatchSize = conf.GetValue("Kafka:Producer:BatchSize"), - BatchNumMessages = conf.GetValue("Kafka:Producer:BatchNumMessages"), - // 发送缓冲内存限制 - QueueBufferingMaxMessages = conf.GetValue("Kafka:Producer:QueueBufferingMaxMessages"), - QueueBufferingMaxKbytes = conf.GetValue("Kafka:Producer:QueueBufferingMaxKbytes"), - // 分区器 一致性hash/随机 - Partitioner = conf.GetValue("Kafka:Producer:Partitioner"), - // Debug = "all", - // LogConnectionClose = false, - // LogQueue = true - }) + var producerConfig = new ProducerConfig + { + ClientId = Dns.GetHostName(), + BootstrapServers = conf.GetValue("Kafka:BootstrapServers"), + // 确保消息发送可靠 + Acks = Acks.All, + EnableIdempotence = true, + MessageSendMaxRetries = conf.GetValue("Kafka:Producer:MessageSendMaxRetries"), + RequestTimeoutMs = conf.GetValue("Kafka:Producer:RequestTimeoutMs"), + // 消息体大小 + CompressionType = conf.GetValue("Kafka:Producer:CompressionType"), + CompressionLevel = conf.GetValue("Kafka:Producer:CompressionLevel"), + // 消息发送速率控制(任意一条件先达到就发送) + LingerMs = conf.GetValue("Kafka:Producer:LingerMs"), + BatchSize = conf.GetValue("Kafka:Producer:BatchSize"), + BatchNumMessages = conf.GetValue("Kafka:Producer:BatchNumMessages"), + // 发送缓冲内存限制 + QueueBufferingMaxMessages = conf.GetValue("Kafka:Producer:QueueBufferingMaxMessages"), + QueueBufferingMaxKbytes = conf.GetValue("Kafka:Producer:QueueBufferingMaxKbytes"), + // 分区器 一致性hash/随机 + Partitioner = conf.GetValue("Kafka:Producer:Partitioner"), + // Debug = "all", + // LogConnectionClose = false, + // LogQueue = true + }; + ApplyOptionalSasl(producerConfig, conf); + + var bb = new ProducerBuilder(producerConfig) .SetKeySerializer(sp.GetRequiredService>()) .SetValueSerializer(sp.GetRequiredService>()) .SetErrorHandler((_, error) => @@ -143,6 +148,21 @@ public static class WarningCalcServiceExtension }); } + /// + /// 本地连外口 kafka-ext(SASL)时配置;集群内 PLAINTEXT 不配 SaslUsername 则跳过。 + /// + private static void ApplyOptionalSasl(ClientConfig cfg, IConfiguration conf) + { + var user = conf.GetValue("Kafka:SaslUsername"); + if (string.IsNullOrWhiteSpace(user)) + return; + + cfg.SecurityProtocol = conf.GetValue("Kafka:SecurityProtocol", SecurityProtocol.SaslPlaintext); + cfg.SaslMechanism = conf.GetValue("Kafka:SaslMechanism", SaslMechanism.Plain); + cfg.SaslUsername = user; + cfg.SaslPassword = conf.GetValue("Kafka:SaslPassword"); + } + private static void AddJsonSerializer(this IServiceCollection services, Action? setup = null) { services.TryAddSingleton(_ => diff --git a/src/TrackCenter.Application/Rabbit/TrackRefreshEventHandler.cs b/src/TrackCenter.Application/Rabbit/TrackRefreshEventHandler.cs index eb5b6206..6d5f68d1 100644 --- a/src/TrackCenter.Application/Rabbit/TrackRefreshEventHandler.cs +++ b/src/TrackCenter.Application/Rabbit/TrackRefreshEventHandler.cs @@ -81,7 +81,7 @@ public class TrackRefreshEventHandler( // 客户A 渠道B 产品A var config = await trackPullCacheService.GetPullParamsWithContentAsync(attach.AppKey, - attach.ServiceProviderCode); + attach.ServiceProviderCode, attach.OrderType); if (config is null) { logger.LogWarning("请求id: {@RequestId},轨迹消费失败,找不到拉取配置:{@Attach}", attach.ToJson(), @event.RequestId); diff --git a/src/TrackCenter.Application/Services/ProvideServices/OrderService.V2.cs b/src/TrackCenter.Application/Services/ProvideServices/OrderService.V2.cs index f8949f36..cbcfc734 100644 --- a/src/TrackCenter.Application/Services/ProvideServices/OrderService.V2.cs +++ b/src/TrackCenter.Application/Services/ProvideServices/OrderService.V2.cs @@ -179,7 +179,8 @@ public partial class OrderService var config = await _trackPullConfigRepository .FirstOrDefaultAsync(x => x.AppKey == AppKey - && x.ServiceProviderCode == input.ApiCode); + && x.ServiceProviderCode == input.ApiCode + && x.OrderType == TrackOrderType.小包); if (config == null) { @@ -192,11 +193,12 @@ public partial class OrderService return order; } - private Task BuiltOrder(UpsertBigBagInput input) + private async Task BuiltOrder(UpsertBigBagInput input) { var order = new OrderEntity { AppKey = AppKey!, + ServiceProviderCode = input.ApiCode, OrderType = TrackOrderType.大包, OrderNo = input.BagNo, ThirdPartyOrderNumber = input.OtherNo, @@ -206,8 +208,25 @@ public partial class OrderService ArrivalCountry = input.Country }; - // tips: 2025-11-29 zzh 大包没有轨迹拉取能力 - return Task.FromResult(order); + if (string.IsNullOrWhiteSpace(input.ApiCode)) + { + // 未传 apiCode 时保持兼容:不挂拉取配置 + return order; + } + + var config = await _trackPullConfigRepository + .FirstOrDefaultAsync(x => x.AppKey == AppKey + && x.ServiceProviderCode == input.ApiCode + && x.OrderType == TrackOrderType.大包); + + if (config == null) + { + throw ProviderServiceActionResult.Exception("创建/更新失败", + "没有适用的大包拉取配置,请先配置轨迹拉取,否则无法拉取轨迹"); + } + + order.OrderPullStatus = [CreateOrderPullStatus(config, order)]; + return order; } private async Task BuiltOrder(UpsertMasterOrderInput input) @@ -227,7 +246,8 @@ public partial class OrderService var config = await _trackPullConfigRepository .FirstOrDefaultAsync(x => x.AppKey == AppKey - && x.ServiceProviderCode == input.ApiCode); + && x.ServiceProviderCode == input.ApiCode + && x.OrderType == TrackOrderType.小包); if (config == null) { diff --git a/src/TrackCenter.Application/Services/ProvideServices/TrackService.Query.cs b/src/TrackCenter.Application/Services/ProvideServices/TrackService.Query.cs index 9120a8e6..44f58896 100644 --- a/src/TrackCenter.Application/Services/ProvideServices/TrackService.Query.cs +++ b/src/TrackCenter.Application/Services/ProvideServices/TrackService.Query.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Http; +using Fake.ObjectStorage; +using Microsoft.AspNetCore.Http; using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Helpers; using TrackCenter.Application.Helpers.OSS; @@ -14,6 +15,7 @@ public partial class TrackService { private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; private readonly IHttpContextAccessor _contextAccessor; + private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService(); /// /// 查询轨迹(时区补正) @@ -37,7 +39,7 @@ public partial class TrackService .SelectMany(x => x.Files!.Select(y => y.OssKey)) .ToArray(); //根据osskey获取下载地址 - var ossKeyUrlDict = OSSHelper.GetUrlByKeys(ossKeys); + var ossKeyUrlDict = await ObjectStorage.GetUrlByKeysAsync(ossKeys); foreach (var group in details.GroupBy(x => x.OrderId)) { @@ -118,7 +120,7 @@ public partial class TrackService .SelectMany(x => x.Files!.Select(y => y.OssKey)) .ToArray(); //根据osskey获取下载地址 - var ossKeyUrlDict = OSSHelper.GetUrlByKeys(ossKeys); + var ossKeyUrlDict = await ObjectStorage.GetUrlByKeysAsync(ossKeys); foreach (var group in details.GroupBy(x => x.OrderId)) { diff --git a/src/TrackCenter.Application/Services/TrackContents/TrackContentService.cs b/src/TrackCenter.Application/Services/TrackContents/TrackContentService.cs index 4be79fe5..60335630 100644 --- a/src/TrackCenter.Application/Services/TrackContents/TrackContentService.cs +++ b/src/TrackCenter.Application/Services/TrackContents/TrackContentService.cs @@ -1,5 +1,6 @@ using System.Linq.Expressions; using Fake.Domain.Dtos; +using Fake.ObjectStorage; using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.TrackContents; using TrackCenter.Application.Helpers; @@ -27,6 +28,7 @@ public class TrackContentService : ApplicationServiceBase private readonly IOrderRepository _orderRepository; private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; private readonly TrackPushCacheService _trackPushCacheService; + private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService(); public TrackContentService( ITrackContentRepository trackContentRepository, @@ -249,7 +251,7 @@ public class TrackContentService : ApplicationServiceBase if (ossKeys.Length == 0) return res; - var ossKeyUrlDict = OSSHelper.GetUrlByKeys(ossKeys); + var ossKeyUrlDict = await ObjectStorage.GetUrlByKeysAsync(ossKeys); foreach (var item in res.Files) { if (string.IsNullOrWhiteSpace(item.OssKey)) continue; diff --git a/src/TrackCenter.Application/Services/TrackOrders/TrackOrderService.cs b/src/TrackCenter.Application/Services/TrackOrders/TrackOrderService.cs index d1af49fc..cb2b2dc2 100644 --- a/src/TrackCenter.Application/Services/TrackOrders/TrackOrderService.cs +++ b/src/TrackCenter.Application/Services/TrackOrders/TrackOrderService.cs @@ -1,5 +1,6 @@ using System.Linq.Expressions; using Fake.Domain.Dtos; +using Fake.ObjectStorage; using TrackCenter.Application.Dtos.ProvideServices; using TrackCenter.Application.Dtos.TrackOrder; using TrackCenter.Application.Helpers; @@ -27,6 +28,7 @@ public class TrackOrderService : ApplicationServiceBase private readonly ITrackDetailsEsRepository _trackDetailsEsRepository; private readonly ITrackPushRecordEsRepository _trackPushRecordEsRepository; private readonly IServiceProvider _serviceProvider; + private IObjectStorage ObjectStorage => LazyServiceProvider.GetRequiredService(); public TrackOrderService( IOrderRepository orderRepository, @@ -215,6 +217,9 @@ public class TrackOrderService : ApplicationServiceBase //时间轴不显示轨迹内容为空的轨迹,进行过滤 trackDetails = trackDetails.Where(x => !x.TrackContent.IsNullOrWhiteSpace() && x.TrackContent != GlobalConst.EmptyString).ToList(); + var ossKeyUrlDict = await ObjectStorage.GetUrlByKeysAsync( + trackDetails.Where(x => x.Files != null).SelectMany(x => x.Files!.Select(f => f.OssKey))); + foreach (var item in trackDetails) { //时间和时区任意一个为空则使用创建时间做为排序参照值 @@ -236,7 +241,7 @@ public class TrackOrderService : ApplicationServiceBase { OssKey = m.OssKey, FileName = m.FileName, - Url = m.OssKey.GetUrlByKey(), + Url = ossKeyUrlDict.TryGetValue(m.OssKey ?? string.Empty, out var u) ? u : string.Empty, }).ToList(), Content = item.TrackContent, TrackCode = item.TrackCode, diff --git a/src/TrackCenter.Application/Services/TrackPull/TrackPullCacheService.cs b/src/TrackCenter.Application/Services/TrackPull/TrackPullCacheService.cs index d44624fe..306580c7 100644 --- a/src/TrackCenter.Application/Services/TrackPull/TrackPullCacheService.cs +++ b/src/TrackCenter.Application/Services/TrackPull/TrackPullCacheService.cs @@ -20,12 +20,12 @@ public class TrackPullCacheService : ITransientDependency $"{GlobalConst.TrackPullKeyPrefix}:{config.AppKey}:{config.ServiceProviderCode}:{config.Customer}"; public async Task GetPullParamsWithContentAsync(string appKey, - string serviceProviderCode) + string serviceProviderCode, TrackOrderType orderType) { var data = await RedisHelper.Client.GetAsync(TrackPullContentKey( - appKey, serviceProviderCode)); + appKey, serviceProviderCode, orderType)); - var pullId = await _trackPullConfigRepository.FindIdAsync(appKey, serviceProviderCode); + var pullId = await _trackPullConfigRepository.FindIdAsync(appKey, serviceProviderCode, orderType); data ??= await SetPullConfigAsync(pullId); return data; @@ -53,12 +53,13 @@ public class TrackPullCacheService : ITransientDependency trackPullContentList) }; - var key = TrackPullContentKey(trackPullConfigDetail.AppKey, trackPullConfigDetail.ServiceProviderCode); + var key = TrackPullContentKey(trackPullConfigDetail.AppKey, trackPullConfigDetail.ServiceProviderCode, + trackPullConfigDetail.OrderType); await RedisHelper.Client.SetAsync(key, data, TimeSpan.FromHours(24)); return data; } - public static string TrackPullContentKey(string appKey, string serviceProviderCode) => - $"{GlobalConst.TrackPullContentKeyPrefix}:{appKey}:{serviceProviderCode}"; + public static string TrackPullContentKey(string appKey, string serviceProviderCode, TrackOrderType orderType) => + $"{GlobalConst.TrackPullContentKeyPrefix}:{appKey}:{serviceProviderCode}:{(int)orderType}"; } diff --git a/src/TrackCenter.Application/Services/TrackPull/TrackPullService.cs b/src/TrackCenter.Application/Services/TrackPull/TrackPullService.cs index 0506cead..8c4b6322 100644 --- a/src/TrackCenter.Application/Services/TrackPull/TrackPullService.cs +++ b/src/TrackCenter.Application/Services/TrackPull/TrackPullService.cs @@ -83,9 +83,13 @@ public class TrackPullService( if (input.Options != OptionTypeNoNull.删除) { + if (!Enum.IsDefined(typeof(TrackOrderType), input.OrderType)) + throw new BusinessException("请选择订单类型"); + Expression> isAnyExp = x => x.AppKey == input.AppKey && - x.ServiceProviderCode == input.ServiceProviderCode; + x.ServiceProviderCode == input.ServiceProviderCode && + x.OrderType == input.OrderType; if (input.Options == OptionTypeNoNull.修改) isAnyExp = isAnyExp.And(x => x.Id != input.Id); if (await trackPullConfigRepository.IsAnyAsync(isAnyExp)) diff --git a/src/TrackCenter.Application/TrackCenter.Application.csproj b/src/TrackCenter.Application/TrackCenter.Application.csproj index 279077ee..d1d3f4ba 100644 --- a/src/TrackCenter.Application/TrackCenter.Application.csproj +++ b/src/TrackCenter.Application/TrackCenter.Application.csproj @@ -11,8 +11,8 @@ + - diff --git a/src/TrackCenter.Application/TrackCenterApplicationModule.cs b/src/TrackCenter.Application/TrackCenterApplicationModule.cs index 5e4d74a7..0137323b 100644 --- a/src/TrackCenter.Application/TrackCenterApplicationModule.cs +++ b/src/TrackCenter.Application/TrackCenterApplicationModule.cs @@ -7,12 +7,14 @@ using Fake; using Fake.EventBus.RabbitMQ; using Fake.Modularity; using Fake.ObjectMapping.AutoMapper; +using Fake.ObjectStorage; using Fake.RabbitMQ; using FreeRedis; using Hangfire; using Hangfire.Redis.StackExchange; using InterfaceForward.Domain.Shared.Helpers; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using RabbitMQ.Client; using Savorboard.CAP.InMemoryMessageQueue; using TrackCenter.Application.Kafka; @@ -26,6 +28,7 @@ namespace TrackCenter.Application; [DependsOn(typeof(FakeRabbitMqModule))] [DependsOn(typeof(FakeObjectMappingAutoMapperModule))] [DependsOn(typeof(FakeEventBusRabbitMqModule))] +[DependsOn(typeof(FakeObjectStorageTencentCosModule))] [DependsOn(typeof(TrackCenterDomainModule))] public class TrackCenterApplicationModule : FakeModule { @@ -89,7 +92,7 @@ public class TrackCenterApplicationModule : FakeModule capOptions.UseInMemoryMessageQueue(); capOptions.UseDashboard(); }).AddSubscribeFilter(); - Log.Information("-- 当前为本地环境,用内存存储和内存队列"); + context.Services.GetInitLogger().LogInformation("-- 当前为本地环境,用内存存储和内存队列"); } #endregion @@ -142,16 +145,6 @@ public class TrackCenterApplicationModule : FakeModule context.Services.Configure( configuration.GetSection("InterfaceRelayParamsConfig")); - // //注册阿里云OSS服务 - // context.Services.AddAliOSS(option => - // { - // option.AccessKeyId = configuration["OSS:AccessKeyId"]; - // option.AccessKeySecret = configuration["OSS:AccessKeySecret"]; - // option.Endpoint = configuration["OSS:Endpoint"]; - // option.BucketName = configuration["OSS:BucketName"]; - // option.ReturnHttpsUrl = true; - // }); - var connectionString = $"{configuration["Redis:Host"]}" + $",password={configuration["Redis:Password"]}" + $",defaultDatabase={configuration["Redis:Database"]}" + diff --git a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderEntity.cs b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderEntity.cs index a4200097..e67214ae 100644 --- a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.OrderAggregate; [SugarTable("t_order")] public class OrderEntity : FullAuditedAggregateRoot { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 订单号 @@ -109,7 +105,7 @@ public class OrderEntity : FullAuditedAggregateRoot /// 目的城市 /// [SugarColumn(ColumnName = "ArrivalCity")] - public string ArrivalCity { get; set; } = null!; + public string ArrivalCity { get; set; } /// /// 目的国家 diff --git a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderFlightEntity.cs b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderFlightEntity.cs index e918625e..f60e5ffc 100644 --- a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderFlightEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderFlightEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.OrderAggregate; [SugarTable("t_order_flight")] public class OrderFlightEntity : CreateAuditedEntity, ISoftDelete { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 班期 diff --git a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderLinkEntity.cs b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderLinkEntity.cs index ce5ababa..bab9c241 100644 --- a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderLinkEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderLinkEntity.cs @@ -8,11 +8,7 @@ namespace TrackCenter.Domain.Aggregates.OrderAggregate; [SugarTable("t_order_link")] public class OrderLinkEntity : CreateAuditedEntity, ISoftDelete { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 订单id diff --git a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderTrackPullStatusEntity.cs b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderTrackPullStatusEntity.cs index 434f26fc..e88bd5ba 100644 --- a/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderTrackPullStatusEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/OrderAggregate/OrderTrackPullStatusEntity.cs @@ -11,11 +11,7 @@ namespace TrackCenter.Domain.Aggregates.OrderAggregate; public class OrderPullStatusEntity : Entity, IHasCreateUserId, IHasCreateTime, IHasUpdateUserId, IHasUpdateTime { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 订单id diff --git a/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentEntity.cs index 36cc297d..c6f975fa 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.TrackContentAggregate; [SugarTable("t_track_content")] public class TrackContentEntity : FullAuditedAggregateRoot { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 轨迹内容代码 diff --git a/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentMapEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentMapEntity.cs index e79c148e..72813dc1 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentMapEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackContentAggregate/TrackContentMapEntity.cs @@ -8,11 +8,7 @@ namespace TrackCenter.Domain.Aggregates.TrackContentAggregate; [SugarTable("t_track_content_map")] public class TrackContentMapEntity : FullAuditedEntity { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 源应用 diff --git a/src/TrackCenter.Domain/Aggregates/TrackFlagAggregate/TrackFlagEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackFlagAggregate/TrackFlagEntity.cs index 567e285a..c056d42e 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackFlagAggregate/TrackFlagEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackFlagAggregate/TrackFlagEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.TrackFlagAggregate; [SugarTable("t_track_flag")] public class TrackFlagEntity : FullAuditedAggregateRoot { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 标记代码 diff --git a/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/ITrackPullConfigRepository.cs b/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/ITrackPullConfigRepository.cs index 492b5874..5e0cbb77 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/ITrackPullConfigRepository.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/ITrackPullConfigRepository.cs @@ -75,5 +75,5 @@ public interface ITrackPullConfigRepository : IBasicRepository IsPriorityAnyAsync(Expression> whereExpression); - Task FindIdAsync(string appKey, string serviceProviderCode); + Task FindIdAsync(string appKey, string serviceProviderCode, TrackOrderType orderType); } diff --git a/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullConfigEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullConfigEntity.cs index 43820fcb..4f90a91f 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullConfigEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullConfigEntity.cs @@ -20,6 +20,12 @@ public class TrackPullConfigEntity : FullAuditedAggregateRoot [SugarColumn(ColumnName = "ServiceProviderCode")] public string ServiceProviderCode { get; set; } + /// + /// 订单类型(主数据枚举TrackOrderType) + /// + [SugarColumn(ColumnName = "OrderType")] + public TrackOrderType OrderType { get; set; } + /// /// 开始轨迹code /// diff --git a/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullContentConfigEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullContentConfigEntity.cs index 15cf081a..62bb150a 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullContentConfigEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackPullAggregate/TrackPullContentConfigEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.TrackPullAggregate; [SugarTable("t_track_pull_content_config")] public class TrackPullContentConfigEntity : FullAuditedEntity { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 轨迹拉取配置id diff --git a/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs index bb77e4ad..b95209ca 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushConfigEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.TrackPushAggregate; [SugarTable("t_track_push_config")] public class TrackPushConfigEntity : FullAuditedAggregateRoot { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 客户(外部主体id) diff --git a/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushContentConfigEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushContentConfigEntity.cs index 1c93394d..10b252dc 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushContentConfigEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackPushAggregate/TrackPushContentConfigEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.TrackPushAggregate; [SugarTable("t_track_push_content_config")] public class TrackPushContentConfigEntity : FullAuditedEntity { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 推送单号类型 diff --git a/src/TrackCenter.Domain/Aggregates/TrackStatusAggregate/TrackStatusEntity.cs b/src/TrackCenter.Domain/Aggregates/TrackStatusAggregate/TrackStatusEntity.cs index 3cedf5a7..771e31c8 100644 --- a/src/TrackCenter.Domain/Aggregates/TrackStatusAggregate/TrackStatusEntity.cs +++ b/src/TrackCenter.Domain/Aggregates/TrackStatusAggregate/TrackStatusEntity.cs @@ -9,11 +9,7 @@ namespace TrackCenter.Domain.Aggregates.TrackStatusAggregate; [SugarTable("t_track_status")] public class TrackStatusEntity : FullAuditedAggregateRoot { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 应用 diff --git a/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigDetailOutputVO.cs b/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigDetailOutputVO.cs index a3892ef5..4a5cbd8f 100644 --- a/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigDetailOutputVO.cs +++ b/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigDetailOutputVO.cs @@ -26,6 +26,11 @@ public class GetTrackPullConfigDetailOutputVO /// public string AppKey { get; set; } + /// + /// 订单类型(主数据枚举TrackOrderType) + /// + public TrackOrderType OrderType { get; set; } + /// /// 轨迹扩展信息参数名集合(逗号分割) /// diff --git a/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigPageOutputVO.cs b/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigPageOutputVO.cs index cce27290..ebe196a5 100644 --- a/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigPageOutputVO.cs +++ b/src/TrackCenter.Domain/Dtos/TrackPull/GetTrackPullConfigPageOutputVO.cs @@ -1,3 +1,5 @@ +using TrackCenter.Domain.Enums; + namespace TrackCenter.Domain.Dtos.TrackPull; public class GetTrackPullConfigPageOutputVO @@ -14,6 +16,11 @@ public class GetTrackPullConfigPageOutputVO /// public string ServiceProvider { get; set; } + /// + /// 服务商code + /// + public string ServiceProviderCode { get; set; } + /// /// 应用 /// @@ -23,4 +30,9 @@ public class GetTrackPullConfigPageOutputVO /// 应用 /// public string AppKey { get; set; } + + /// + /// 订单类型(主数据枚举TrackOrderType) + /// + public TrackOrderType OrderType { get; set; } } diff --git a/src/TrackCenter.Domain/VEntities/VServiceProviderEntity.cs b/src/TrackCenter.Domain/VEntities/VServiceProviderEntity.cs index 4fe1d4b8..3ef81a9a 100644 --- a/src/TrackCenter.Domain/VEntities/VServiceProviderEntity.cs +++ b/src/TrackCenter.Domain/VEntities/VServiceProviderEntity.cs @@ -8,11 +8,7 @@ namespace TrackCenter.Domain.VEntities; [SugarTable("v_service_provider")] public class VServiceProviderEntity : FullAuditedEntity { - /// - /// 自增主键 - /// - [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, IsIdentity = true)] - public int Id { get; set; } + /// /// 授权接口Code diff --git a/src/TrackCenter.Repository/ES/EsExtensions.cs b/src/TrackCenter.Repository/ES/EsExtensions.cs index 2b0e1578..faed3b8e 100644 --- a/src/TrackCenter.Repository/ES/EsExtensions.cs +++ b/src/TrackCenter.Repository/ES/EsExtensions.cs @@ -9,38 +9,61 @@ namespace TrackCenter.Repository.ES; /// public static class EsExtensions { + /// + /// 月度索引读请求:走 {index}-* 通配符,无匹配索引时返回空结果(首次部署 / 尚未创建别名)。 + /// + public static SearchDescriptor UseMonthlyIndex(this SearchDescriptor descriptor, string indexPrefix) + where T : class + { + return descriptor + .Index($"{indexPrefix}-*") + .IgnoreUnavailable() + .AllowNoIndices(); + } + + /// + /// 月度索引按查询更新:目标索引不存在时视为无匹配文档。 + /// + public static UpdateByQueryDescriptor UseMonthlyIndex(this UpdateByQueryDescriptor descriptor, + string indexPrefix) + where T : class + { + return descriptor + .Index($"{indexPrefix}-*") + .IgnoreUnavailable() + .AllowNoIndices(); + } + + /// + /// 月度索引按查询删除:目标索引不存在时视为无匹配文档。 + /// + public static DeleteByQueryDescriptor UseMonthlyIndex(this DeleteByQueryDescriptor descriptor, + string indexPrefix) + where T : class + { + return descriptor + .Index($"{indexPrefix}-*") + .IgnoreUnavailable() + .AllowNoIndices(); + } + /// /// 轨迹明细实际写入月度索引 track_details-yyyy.MM;读请求走通配符, /// 无匹配索引时返回空结果(首次部署 / 尚未 Migrate 别名)。 /// public static SearchDescriptor UseTrackDetailsIndex(this SearchDescriptor descriptor) where T : class - { - return descriptor - .Index($"{GlobalConst.TrackDetailsIndex}-*") - .IgnoreUnavailable() - .AllowNoIndices(); - } + => descriptor.UseMonthlyIndex(GlobalConst.TrackDetailsIndex); /// /// 轨迹明细按查询更新:目标索引不存在时视为无匹配文档。 /// public static UpdateByQueryDescriptor UseTrackDetailsIndex(this UpdateByQueryDescriptor descriptor) where T : class - { - return descriptor - .Index($"{GlobalConst.TrackDetailsIndex}-*") - .IgnoreUnavailable() - .AllowNoIndices(); - } + => descriptor.UseMonthlyIndex(GlobalConst.TrackDetailsIndex); public static DeleteByQueryDescriptor UseTrackDetailsIndex(this DeleteByQueryDescriptor descriptor) where T : class - { - return descriptor - .Index($"{GlobalConst.TrackDetailsIndex}-*") - .IgnoreUnavailable() - .AllowNoIndices(); - } + => descriptor.UseMonthlyIndex(GlobalConst.TrackDetailsIndex); /// /// 索引/别名不存在(含 NEST 对 404 的包装错误)。 diff --git a/src/TrackCenter.Repository/Repositories/OrderOperationRecordEsRepository.cs b/src/TrackCenter.Repository/Repositories/OrderOperationRecordEsRepository.cs index 50332507..751e6102 100644 --- a/src/TrackCenter.Repository/Repositories/OrderOperationRecordEsRepository.cs +++ b/src/TrackCenter.Repository/Repositories/OrderOperationRecordEsRepository.cs @@ -67,13 +67,21 @@ public class OrderOperationRecordEsRepository : ElasticSearchContextAbstract> GetOrderOperationsByIdAsync(int orderId) { var data = await Context.SearchAsync(x => x - .Index(GlobalConst.OrderOperationRecordIndex) + .UseMonthlyIndex(GlobalConst.OrderOperationRecordIndex) .Query(q => q.Term(t => t.OrderId, orderId) ) .Size(GlobalConst.NormalLimit) .Sort(s => s.Field(f => f.CreateTime, SortOrder.Descending))); + if (!data.IsValid) + { + if (data.IsIndexUnavailable()) + return []; + + throw new InvalidOperationException($"订单操作记录查询失败,原因:{data.DebugInformation}。"); + } + return data.ToIdList(); } } diff --git a/src/TrackCenter.Repository/Repositories/TrackPullConfigRepository.cs b/src/TrackCenter.Repository/Repositories/TrackPullConfigRepository.cs index 08bea8ee..8e16bfe2 100644 --- a/src/TrackCenter.Repository/Repositories/TrackPullConfigRepository.cs +++ b/src/TrackCenter.Repository/Repositories/TrackPullConfigRepository.cs @@ -5,6 +5,7 @@ using TrackCenter.Domain.Aggregates.TrackPullAggregate; using TrackCenter.Domain.Aggregates.TrackStatusAggregate; using TrackCenter.Domain.Dtos.Common; using TrackCenter.Domain.Dtos.TrackPull; +using TrackCenter.Domain.Enums; using TrackCenter.Domain.VEntities; namespace TrackCenter.Repository.Repositories; @@ -27,6 +28,8 @@ public class TrackPullConfigRepository : BasicRepository, AppKey = system.AppKey, App = system.Name, ServiceProvider = service.Name, + ServiceProviderCode = service.Code, + OrderType = trackPull.OrderType, CreateTime = trackPull.CreateTime }) .ToPagedListAsync(input.PageIndex, input.PageSize); @@ -216,11 +219,12 @@ public class TrackPullConfigRepository : BasicRepository, return await Context.Queryable().Where(whereExpression).AnyAsync(); } - public async Task FindIdAsync(string appKey, string serviceProviderCode) + public async Task FindIdAsync(string appKey, string serviceProviderCode, TrackOrderType orderType) { return await AsQueryable() .Where(trackPull => trackPull.AppKey == appKey - && trackPull.ServiceProviderCode == serviceProviderCode) + && trackPull.ServiceProviderCode == serviceProviderCode + && trackPull.OrderType == orderType) .Select(trackPull => trackPull.Id) .FirstAsync(); } diff --git a/src/TrackCenter.Repository/Repositories/TrackPushRecordEsRepository.cs b/src/TrackCenter.Repository/Repositories/TrackPushRecordEsRepository.cs index 1424e571..2b6cf7b6 100644 --- a/src/TrackCenter.Repository/Repositories/TrackPushRecordEsRepository.cs +++ b/src/TrackCenter.Repository/Repositories/TrackPushRecordEsRepository.cs @@ -69,13 +69,21 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract> GetTrackPushRecordsAsync(int orderId) { var data = await Context.SearchAsync(x => x - .Index(GlobalConst.TrackPushRecordIndex) + .UseMonthlyIndex(GlobalConst.TrackPushRecordIndex) .Query(q => q.Term(t => t.OrderId, orderId) ) .Size(1000) .Sort(s => s.Field(f => f.CreateTime, SortOrder.Descending))); + if (!data.IsValid) + { + if (data.IsIndexUnavailable()) + return []; + + throw new InvalidOperationException($"轨迹推送记录查询失败,原因:{data.DebugInformation}。"); + } + return data.ToIdList(); } @@ -114,7 +122,7 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract(x => x - .Index(GlobalConst.TrackPushRecordIndex) + .UseMonthlyIndex(GlobalConst.TrackPushRecordIndex) .Query(q => q.Bool(b => b .Must(mu => mu.Terms(t => t.Field(f => f.IsSuccess).Terms(input.IsSuccess))) .Should(musts) @@ -126,7 +134,7 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract mu.Terms(t => t.Field(f => f.IsSuccess).Terms(input.IsSuccess))); r = await Context.SearchAsync(x => x - .Index(GlobalConst.TrackPushRecordIndex) + .UseMonthlyIndex(GlobalConst.TrackPushRecordIndex) .Query(q => q.Bool(b => b .Must(musts)) ) @@ -136,6 +144,9 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract mu.Term(t => t.IsSuccess, true)); var data = await Context.SearchAsync(x => x - .Index(GlobalConst.TrackPushRecordIndex) + .UseMonthlyIndex(GlobalConst.TrackPushRecordIndex) .Query(q => q.Bool(b => b.Must(musts)) @@ -161,13 +172,21 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract s.Field(f => f.CreateTime, SortOrder.Descending))); + if (!data.IsValid) + { + if (data.IsIndexUnavailable()) + return null; + + throw new InvalidOperationException($"轨迹推送记录查询失败,原因:{data.DebugInformation}。"); + } + return data.Documents.FirstOrDefault(); } public async Task> GetHistorySuccessTrackPushRecordAsync(int orderId) { var data = await Context.SearchAsync(x => x - .Index(GlobalConst.TrackPushRecordIndex) + .UseMonthlyIndex(GlobalConst.TrackPushRecordIndex) .Source(s => s.Excludes(fs => fs.Field(f => f.PushContent))) @@ -179,6 +198,15 @@ public class TrackPushRecordEsRepository : ElasticSearchContextAbstract s.Field(f => f.CreateTime, SortOrder.Descending))); + + if (!data.IsValid) + { + if (data.IsIndexUnavailable()) + return []; + + throw new InvalidOperationException($"轨迹推送记录查询失败,原因:{data.DebugInformation}。"); + } + return data.ToIdList(); } } diff --git a/src/TrackCenter.Repository/Repositories/WarningDetailsEsRepository.cs b/src/TrackCenter.Repository/Repositories/WarningDetailsEsRepository.cs index fcb6b327..415d65ae 100644 --- a/src/TrackCenter.Repository/Repositories/WarningDetailsEsRepository.cs +++ b/src/TrackCenter.Repository/Repositories/WarningDetailsEsRepository.cs @@ -50,7 +50,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien c => c.Match(f => f.Field(d => d.NotifyUsers).Query(input.NotifyUserId))); var r = await _client.SearchAsync(x => x - .Index(GlobalConst.TransportWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.TransportWarningDetailsIndex) .Query(q => q.Bool(b => b.Must(musts))) .Sort(s => s.Ascending(f => f.Status).Descending(f => f.CreateTime)) .From((input.PageIndex - 1) * input.PageSize) @@ -60,6 +60,9 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (!r.IsValid) { + if (r.IsIndexUnavailable()) + return new PagedResult(0, []); + throw new InvalidOperationException($"运输预警明细查询失败,原因:{r.DebugInformation}。"); } @@ -116,7 +119,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (isShouldQuery) { r = await _client.SearchAsync(x => x - .Index(GlobalConst.TransportWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.TransportWarningDetailsIndex) .Query(q => q.Bool(b => b .Must(mu => mu.Terms(t => t.Field(f => f.Status).Terms(input.Status))) .Should(musts) @@ -128,7 +131,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien { musts.Add(mu => mu.Terms(t => t.Field(f => f.Status).Terms(input.Status))); r = await _client.SearchAsync(x => x - .Index(GlobalConst.TransportWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.TransportWarningDetailsIndex) .Query(q => q.Bool(b => b.Must(musts))) .Size(SafelyLimit) ); @@ -136,6 +139,9 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (!r.IsValid) { + if (r.IsIndexUnavailable()) + return []; + throw new InvalidOperationException($"运输预警明细查询失败,原因:{r.DebugInformation}。"); } @@ -294,7 +300,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien musts.AddIf(x => x.HasOrderIds, c => c.Terms(t => t.Field(f => f.OrderId).Terms(input.OrderIds))); var r = await _client.UpdateByQueryAsync(u => - u.Index(GlobalConst.TransportWarningDetailsIndex) + u.UseMonthlyIndex(GlobalConst.TransportWarningDetailsIndex) .Query(q => q.Bool(b => b.Must(musts))) .Script(s => s .Source(@"ctx._source.status = params.status; @@ -310,7 +316,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien ) ); - if (!r.IsValid) + if (!r.IsValid && !r.IsIndexUnavailable()) { throw new InvalidOperationException( $"运输时效预警明细更新失败,input:{input.ToJson()},原因:{r.DebugInformation}。"); @@ -387,7 +393,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien c => c.Match(f => f.Field(d => d.Customer).Query(input.Customer))); var r = await _client.SearchAsync(x => x - .Index(GlobalConst.ReturnWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.ReturnWarningDetailsIndex) .Query(q => q.Bool(b => b.Must(musts))) .Sort(s => s.Ascending(f => f.Status).Descending(f => f.CreateTime)) .From((input.PageIndex - 1) * input.PageSize) @@ -397,6 +403,9 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (!r.IsValid) { + if (r.IsIndexUnavailable()) + return new PagedResult(0, []); + throw new InvalidOperationException($"回传预警明细查询失败,原因:{r.DebugInformation}。"); } @@ -442,7 +451,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (isShouldQuery) { r = await _client.SearchAsync(x => x - .Index(GlobalConst.ReturnWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.ReturnWarningDetailsIndex) .Query(q => q.Bool(b => b .Must(mu => mu.Terms(t => t.Field(f => f.Status).Terms(input.Status))) .Should(musts) @@ -454,7 +463,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien { musts.Add(c => c.Terms(t => t.Field(f => f.Status).Terms(input.Status))); r = await _client.SearchAsync(x => x - .Index(GlobalConst.ReturnWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.ReturnWarningDetailsIndex) .Query(q => q.Bool(b => b .Must(musts) )) @@ -464,6 +473,9 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (!r.IsValid) { + if (r.IsIndexUnavailable()) + return []; + throw new InvalidOperationException($"回传预警明细查询失败,原因:{r.DebugInformation}。"); } @@ -473,13 +485,16 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien public async Task> GetReturnMessageRecordListByIdAsync(string esId) { var r = await _client.SearchAsync(x => x - .Index(GlobalConst.ReturnMessageRecordIndex) + .UseMonthlyIndex(GlobalConst.ReturnMessageRecordIndex) .Query(q => q.Match(f => f.Field(d => d.WarningDetailsIds).Query(esId))) .Size(SafelyLimit) ); if (!r.IsValid) { + if (r.IsIndexUnavailable()) + return []; + throw new InvalidOperationException($"回传预警明细查询失败,原因:{r.DebugInformation}。"); } @@ -489,13 +504,13 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien public async Task ReturnWarningDetailsRetryIncrementAsync(HashSet esIds) { var r = await _client.UpdateByQueryAsync(u => - u.Index(GlobalConst.ReturnWarningDetailsIndex) + u.UseMonthlyIndex(GlobalConst.ReturnWarningDetailsIndex) .Query(q => q.Ids(i => i.Values(esIds))) .Script(s => s .Source("ctx._source.retryCount = ctx._source.retryCount+1;") ) ); - if (!r.IsValid) + if (!r.IsValid && !r.IsIndexUnavailable()) { throw new InvalidOperationException($"回传预警明细更新失败,原因:{r.DebugInformation}。"); } @@ -661,7 +676,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien musts.AddIf(x => x.HasOrderIds, c => c.Terms(t => t.Field(f => f.OrderId).Terms(input.OrderIds))); var r = await _client.UpdateByQueryAsync(u => - u.Index(GlobalConst.ReturnWarningDetailsIndex) + u.UseMonthlyIndex(GlobalConst.ReturnWarningDetailsIndex) .Query(q => q.Bool(b => b.Must(musts))) .Script(s => s .Source("ctx._source.status = params.status") @@ -669,7 +684,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien ) ); - if (!r.IsValid) + if (!r.IsValid && !r.IsIndexUnavailable()) { throw new InvalidOperationException( $"回传预警明细更新失败,input:{input.ToJson()},原因:{r.DebugInformation}。"); @@ -766,7 +781,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien .Params(p => p.Add("value", input.MissingTrackStatusId.ToString()))))); var r = await _client.SearchAsync(x => x - .Index(GlobalConst.CompleteWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.CompleteWarningDetailsIndex) .Query(q => q.Bool(b => b.Must(musts))) .Sort(s => s.Ascending(f => f.Status).Descending(f => f.CreateTime)) .From((input.PageIndex - 1) * input.PageSize) @@ -776,6 +791,9 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (!r.IsValid) { + if (r.IsIndexUnavailable()) + return new PagedResult(0, []); + throw new InvalidOperationException($"回传预警明细查询失败,原因:{r.DebugInformation}。"); } @@ -830,7 +848,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (isShouldQuery) { r = await _client.SearchAsync(x => x - .Index(GlobalConst.CompleteWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.CompleteWarningDetailsIndex) .Query(q => q.Bool(b => b .Must(mu => mu.Terms(t => t.Field(f => f.Status).Terms(input.Status))) .Should(musts) @@ -842,7 +860,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien { musts.Add(c => c.Terms(t => t.Field(f => f.Status).Terms(input.Status))); r = await _client.SearchAsync(x => x - .Index(GlobalConst.CompleteWarningDetailsIndex) + .UseMonthlyIndex(GlobalConst.CompleteWarningDetailsIndex) .Query(q => q.Bool(b => b .Must(musts) )) @@ -852,6 +870,9 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien if (!r.IsValid) { + if (r.IsIndexUnavailable()) + return []; + throw new InvalidOperationException($"完整预警明细查询失败,原因:{r.DebugInformation}。"); } @@ -872,7 +893,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien musts.AddIf(x => x.HasOrderIds, c => c.Terms(t => t.Field(f => f.OrderId).Terms(input.OrderIds))); var r = await _client.UpdateByQueryAsync(u => - u.Index(GlobalConst.CompleteWarningDetailsIndex) + u.UseMonthlyIndex(GlobalConst.CompleteWarningDetailsIndex) .Query(q => q.Bool(b => b.Must(musts))) .Script(s => s .Source(@" @@ -886,7 +907,7 @@ public class WarningDetailsEsRepository : IWarningDetailsEsRepository, ITransien ) ); - if (!r.IsValid) + if (!r.IsValid && !r.IsIndexUnavailable()) { throw new InvalidOperationException( $"完整预警明细更新失败,input:{input.ToJson()},原因:{r.DebugInformation}。"); diff --git a/src/TrackCenter.Repository/TrackCenterDbContext.cs b/src/TrackCenter.Repository/TrackCenterDbContext.cs index 35663736..e3b57232 100644 --- a/src/TrackCenter.Repository/TrackCenterDbContext.cs +++ b/src/TrackCenter.Repository/TrackCenterDbContext.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Fake; using Fake.Domain.Entities; using Fake.SqlSugarCore; @@ -18,5 +19,10 @@ public class TrackCenterDbContext : SugarDbContext } base.ConfigureEntityService(property, column); + + if (property.Name == nameof(Entity.Id)) + { + column.IsIdentity = true; + } } }