feat: 优化es idx 404
All checks were successful
Build and Deploy / deploy (push) Successful in 1m17s

This commit is contained in:
xiaolipro 2026-09-11 15:44:05 +08:00
parent cdff57f8c3
commit a39856db9c
2 changed files with 129 additions and 22 deletions

View File

@ -1,5 +1,5 @@
using TrackCenter.Repository.ES;
using Nest; using Nest;
using TrackCenter.Domain.Consts;
using TrackCenter.Domain.SeedWork; using TrackCenter.Domain.SeedWork;
namespace TrackCenter.Repository.ES; namespace TrackCenter.Repository.ES;
@ -9,12 +9,74 @@ namespace TrackCenter.Repository.ES;
/// </summary> /// </summary>
public static class EsExtensions public static class EsExtensions
{ {
/// <summary>
/// 轨迹明细实际写入月度索引 track_details-yyyy.MM;读请求走通配符,
/// 无匹配索引时返回空结果(首次部署 / 尚未 Migrate 别名)。
/// </summary>
public static SearchDescriptor<T> UseTrackDetailsIndex<T>(this SearchDescriptor<T> descriptor) where T : class
{
return descriptor
.Index($"{GlobalConst.TrackDetailsIndex}-*")
.IgnoreUnavailable()
.AllowNoIndices();
}
/// <summary>
/// 轨迹明细按查询更新:目标索引不存在时视为无匹配文档。
/// </summary>
public static UpdateByQueryDescriptor<T> UseTrackDetailsIndex<T>(this UpdateByQueryDescriptor<T> descriptor)
where T : class
{
return descriptor
.Index($"{GlobalConst.TrackDetailsIndex}-*")
.IgnoreUnavailable()
.AllowNoIndices();
}
public static DeleteByQueryDescriptor<T> UseTrackDetailsIndex<T>(this DeleteByQueryDescriptor<T> descriptor)
where T : class
{
return descriptor
.Index($"{GlobalConst.TrackDetailsIndex}-*")
.IgnoreUnavailable()
.AllowNoIndices();
}
/// <summary>
/// 索引/别名不存在(含 NEST 对 404 的包装错误)。
/// </summary>
public static bool IsIndexUnavailable(this IResponse response)
{
if (response.IsValid)
return false;
if (response.ApiCall?.HttpStatusCode == 404)
return true;
if (IsIndexNotFoundType(response.ServerError?.Error?.Type)
|| IsIndexNotFoundType(response.ServerError?.Error?.CausedBy?.Type))
return true;
var rootCauses = response.ServerError?.Error?.RootCause;
if (rootCauses != null && rootCauses.Any(x => IsIndexNotFoundType(x.Type)))
return true;
// NEST: Invalid NEST response built from a successful (404) low level call on POST: /track_details/_search
var debug = response.DebugInformation;
if (!string.IsNullOrEmpty(debug)
&& (debug.Contains("index_not_found_exception", StringComparison.OrdinalIgnoreCase)
|| debug.Contains("successful (404)", StringComparison.OrdinalIgnoreCase)))
return true;
return false;
}
private static bool IsIndexNotFoundType(string? type) =>
string.Equals(type, "index_not_found_exception", StringComparison.OrdinalIgnoreCase);
/// <summary> /// <summary>
/// return list with id /// return list with id
/// </summary> /// </summary>
/// <param name="response"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static List<T> ToIdList<T>(this ISearchResponse<T> response) where T : IdBaseIndex public static List<T> ToIdList<T>(this ISearchResponse<T> response) where T : IdBaseIndex
{ {
var dtos = new List<T>(); var dtos = new List<T>();

View File

@ -25,7 +25,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
{ {
var r = forUpsert var r = forUpsert
? await Context.SearchAsync<TrackDetailsIndex>(s => s ? await Context.SearchAsync<TrackDetailsIndex>(s => s
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Source() .Source()
.Version() .Version()
.Size(SafelyLimit) .Size(SafelyLimit)
@ -39,7 +39,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
) )
) )
: await Context.SearchAsync<TrackDetailsIndex>(s => s : await Context.SearchAsync<TrackDetailsIndex>(s => s
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Version() .Version()
.Source(s2 => s2.Includes(fd => fd .Source(s2 => s2.Includes(fd => fd
.Field(f => f.IndexName) .Field(f => f.IndexName)
@ -60,6 +60,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new List<TrackDetailsIndex>();
throw new InvalidOperationException( throw new InvalidOperationException(
$"轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],trackCode:{trackCode},原因:{r.DebugInformation}。"); $"轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],trackCode:{trackCode},原因:{r.DebugInformation}。");
} }
@ -126,7 +129,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (isShouldQuery) if (isShouldQuery)
{ {
r = await Context.SearchAsync<TrackDetailsIndex>(x => x r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Version() .Version()
.Query(q => q.Bool(b => b .Query(q => q.Bool(b => b
.Should(musts) .Should(musts)
@ -137,7 +140,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
else else
{ {
r = await Context.SearchAsync<TrackDetailsIndex>(x => x r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Version() .Version()
.Query(q => q.Bool(b => b .Query(q => q.Bool(b => b
.Must(musts) .Must(musts)
@ -148,6 +151,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new Dictionary<int, List<TrackDetailsIndex>>();
throw new InvalidOperationException($"运输预警明细查询失败,原因:{r.DebugInformation}。"); throw new InvalidOperationException($"运输预警明细查询失败,原因:{r.DebugInformation}。");
} }
@ -159,7 +165,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task<Dictionary<int, Dictionary<int, TrackDetailsIndex>>> GetTrackStatusEarliestDictAsync(HashSet<int> orderIds) public async Task<Dictionary<int, Dictionary<int, TrackDetailsIndex>>> GetTrackStatusEarliestDictAsync(HashSet<int> orderIds)
{ {
var response = await Context.SearchAsync<TrackDetailsIndex>(s => s var response = await Context.SearchAsync<TrackDetailsIndex>(s => s
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Size(0) // 不需要实际文档,只需要聚合结果 .Size(0) // 不需要实际文档,只需要聚合结果
.Query(q => q .Query(q => q
.Terms(t => t .Terms(t => t
@ -191,6 +197,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!response.IsValid) if (!response.IsValid)
{ {
if (response.IsIndexUnavailable())
return new Dictionary<int, Dictionary<int, TrackDetailsIndex>>();
throw new InvalidOperationException( throw new InvalidOperationException(
$"最新轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],原因:{response.DebugInformation}。"); $"最新轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],原因:{response.DebugInformation}。");
} }
@ -226,7 +235,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task<Dictionary<int, TrackDetailsIndex>> GetLatestDictAsync(HashSet<int> orderIds) public async Task<Dictionary<int, TrackDetailsIndex>> GetLatestDictAsync(HashSet<int> orderIds)
{ {
var r = await Context.SearchAsync<TrackDetailsIndex>(s => s var r = await Context.SearchAsync<TrackDetailsIndex>(s => s
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Size(0) // 不需要实际文档,只需要聚合结果 .Size(0) // 不需要实际文档,只需要聚合结果
.Query(q => q .Query(q => q
.Terms(t => t .Terms(t => t
@ -253,6 +262,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new Dictionary<int, TrackDetailsIndex>();
throw new InvalidOperationException( throw new InvalidOperationException(
$"最新轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],原因:{r.DebugInformation}。"); $"最新轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],原因:{r.DebugInformation}。");
} }
@ -281,7 +293,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
var excludeTrackCodes = trackCodes.ToList(); var excludeTrackCodes = trackCodes.ToList();
excludeTrackCodes.Add(GlobalConst.EmptyString); excludeTrackCodes.Add(GlobalConst.EmptyString);
var r = await Context.SearchAsync<TrackDetailsIndex>(x => x var r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Source(s => s.Includes(fd => fd .Source(s => s.Includes(fd => fd
.Field(f => f.TrackEnContent) .Field(f => f.TrackEnContent)
.Field(f => f.TrackContent) .Field(f => f.TrackContent)
@ -335,6 +347,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new List<TrackDetailsIndex>();
throw new InvalidOperationException( throw new InvalidOperationException(
$"最新轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],trackCodes:[{trackCodes.JoinAsString(", ")}],原因:{r.DebugInformation}。"); $"最新轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],trackCodes:[{trackCodes.JoinAsString(", ")}],原因:{r.DebugInformation}。");
} }
@ -345,7 +360,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task<bool> AnyAsync(int orderId, string originTrackDataHash) public async Task<bool> AnyAsync(int orderId, string originTrackDataHash)
{ {
var r = await Context.SearchAsync<TrackDetailsIndex>(x => x var r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Source(s => s.Includes(fd => fd.Field(f => f.TrackCode))) .Source(s => s.Includes(fd => fd.Field(f => f.TrackCode)))
.Query(q => .Query(q =>
q.Term(f => f.OrderId, orderId) q.Term(f => f.OrderId, orderId)
@ -355,6 +370,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return false;
throw new InvalidOperationException( throw new InvalidOperationException(
$"轨迹查询失败,orderId:{orderId},hash:{originTrackDataHash},原因:{r.DebugInformation}。"); $"轨迹查询失败,orderId:{orderId},hash:{originTrackDataHash},原因:{r.DebugInformation}。");
} }
@ -412,7 +430,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
while (retryCount < maxRetries && !updateSuccessful) while (retryCount < maxRetries && !updateSuccessful)
{ {
response = await Context.UpdateByQueryAsync<TrackDetailsIndex>(u => u response = await Context.UpdateByQueryAsync<TrackDetailsIndex>(u => u
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Query(q => q .Query(q => q
.Ids(t => t.Values(indexes.Select(x => x.Id))) .Ids(t => t.Values(indexes.Select(x => x.Id)))
) )
@ -465,7 +483,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
) )
); );
if (response.IsValid) if (response.IsValid || response.IsIndexUnavailable())
{ {
updateSuccessful = true; updateSuccessful = true;
} }
@ -542,7 +560,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
var r = await PollyHelper.RetryPollyExecAsync(() => Context.BulkAsync(desc), var r = await PollyHelper.RetryPollyExecAsync(() => Context.BulkAsync(desc),
successValidator: response => successValidator: response =>
{ {
if (response.IsValid) // 认为成功 if (response.IsValid || response.IsIndexUnavailable()) // 认为成功或索引不存在
return true; return true;
if (response.ServerError.Status == 409) //版本冲突认为失败,则重试 if (response.ServerError.Status == 409) //版本冲突认为失败,则重试
@ -620,7 +638,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
var r = await Context.SearchAsync<TrackDetailsIndex>(x => x var r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Source() .Source()
.Version() .Version()
.Query(q => .Query(q =>
@ -637,6 +655,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new List<TrackDetailsIndex>();
throw new InvalidOperationException( throw new InvalidOperationException(
$"轨迹查询失败,orderType:{orderType},orderNos:{orderNos},原因:{r.DebugInformation}。"); $"轨迹查询失败,orderType:{orderType},orderNos:{orderNos},原因:{r.DebugInformation}。");
} }
@ -647,7 +668,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task<List<TrackDetailsIndex>> GetListAsync(int orderId) public async Task<List<TrackDetailsIndex>> GetListAsync(int orderId)
{ {
var r = await Context.SearchAsync<TrackDetailsIndex>(x => x var r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Version() .Version()
.Query(q => .Query(q =>
q.Term(t => t.OrderId, orderId) q.Term(t => t.OrderId, orderId)
@ -660,6 +681,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new List<TrackDetailsIndex>();
throw new InvalidOperationException($"轨迹查询失败,orderId:{orderId},原因:{r.DebugInformation}。"); throw new InvalidOperationException($"轨迹查询失败,orderId:{orderId},原因:{r.DebugInformation}。");
} }
@ -686,7 +710,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
} }
var r = await Context.SearchAsync<TrackDetailsIndex>(x => x var r = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Version() .Version()
.Query(_ => query) .Query(_ => query)
.Size(SafelyLimit) .Size(SafelyLimit)
@ -697,6 +721,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new List<TrackDetailsIndex>();
throw new InvalidOperationException($"轨迹查询失败,orderIds:[{string.Join(',', orderIds)}],原因:{r.DebugInformation}。"); throw new InvalidOperationException($"轨迹查询失败,orderIds:[{string.Join(',', orderIds)}],原因:{r.DebugInformation}。");
} }
@ -720,7 +747,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
// } // }
var search = await Context.SearchAsync<TrackDetailsIndex>(x => x var search = await Context.SearchAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Version() .Version()
.Source(s => s.Includes(fd => fd .Source(s => s.Includes(fd => fd
.Field(f => f.IndexName) .Field(f => f.IndexName)
@ -736,6 +763,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!search.IsValid) if (!search.IsValid)
{ {
if (search.IsIndexUnavailable())
return new List<int>();
throw new InvalidOperationException( throw new InvalidOperationException(
$"轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],trackCodes:[{trackCodes.JoinAsString(", ")}],原因:{search.DebugInformation}。"); $"轨迹查询失败,orderIds:[{orderIds.JoinAsString(", ")}],trackCodes:[{trackCodes.JoinAsString(", ")}],原因:{search.DebugInformation}。");
} }
@ -774,12 +804,15 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task DeleteTrackByIdsAsync(HashSet<string> esIds) public async Task DeleteTrackByIdsAsync(HashSet<string> esIds)
{ {
var r = await Context.DeleteByQueryAsync<TrackDetailsIndex>(x => x var r = await Context.DeleteByQueryAsync<TrackDetailsIndex>(x => x
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Query(q => q.Ids(i => i.Values(esIds))) .Query(q => q.Ids(i => i.Values(esIds)))
); );
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return;
throw new InvalidOperationException($"轨迹删除失败,ids:[{string.Join(", ", esIds)}],原因:{r.DebugInformation}。"); throw new InvalidOperationException($"轨迹删除失败,ids:[{string.Join(", ", esIds)}],原因:{r.DebugInformation}。");
} }
} }
@ -787,7 +820,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task<List<TrackDetailsIndex>> GetDataByIds(string[] ids) public async Task<List<TrackDetailsIndex>> GetDataByIds(string[] ids)
{ {
var r = await Context.SearchAsync<TrackDetailsIndex>(m => m var r = await Context.SearchAsync<TrackDetailsIndex>(m => m
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Version() .Version()
.Query( .Query(
q => q.Ids(i => i.Values(ids)) q => q.Ids(i => i.Values(ids))
@ -795,6 +828,9 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
if (!r.IsValid) if (!r.IsValid)
{ {
if (r.IsIndexUnavailable())
return new List<TrackDetailsIndex>();
throw new InvalidOperationException($"轨迹查询失败,ids:[{string.Join(", ", ids)}],原因:{r.DebugInformation}。"); throw new InvalidOperationException($"轨迹查询失败,ids:[{string.Join(", ", ids)}],原因:{r.DebugInformation}。");
} }
@ -804,7 +840,7 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
public async Task<TrackDetailsIndex?> GetDetailAsync(string orderNo, string trackCode, TrackOrderType orderType, string appKey, DateTime trackTime) public async Task<TrackDetailsIndex?> GetDetailAsync(string orderNo, string trackCode, TrackOrderType orderType, string appKey, DateTime trackTime)
{ {
var res = await Context.SearchAsync<TrackDetailsIndex>(s => s var res = await Context.SearchAsync<TrackDetailsIndex>(s => s
.Index(GlobalConst.TrackDetailsIndex) .UseTrackDetailsIndex()
.Query(q => q .Query(q => q
.Bool(b => b .Bool(b => b
.Must(mu => mu .Must(mu => mu
@ -832,6 +868,15 @@ public class TrackDetailsEsRepository : ElasticSearchContextAbstract<TrackDetail
) )
); );
if (!res.IsValid)
{
if (res.IsIndexUnavailable())
return null;
throw new InvalidOperationException(
$"轨迹查询失败,orderNo:{orderNo},trackCode:{trackCode},原因:{res.DebugInformation}。");
}
return res.Documents.FirstOrDefault(); return res.Documents.FirstOrDefault();
} }