feat(geo): add tenant/merchant/store geocode fallback and retry workflow
All checks were successful
Build and Deploy TenantApi / build-and-deploy (push) Successful in 43s

This commit is contained in:
2026-02-19 17:13:00 +08:00
parent ad245078a2
commit 53f7c54c82
33 changed files with 9514 additions and 11 deletions

View File

@@ -5,6 +5,7 @@ using TakeoutSaaS.Domain.Coupons.Entities;
using TakeoutSaaS.Domain.CustomerService.Entities;
using TakeoutSaaS.Domain.Deliveries.Entities;
using TakeoutSaaS.Domain.Distribution.Entities;
using TakeoutSaaS.Domain.Common.Enums;
using TakeoutSaaS.Domain.Engagement.Entities;
using TakeoutSaaS.Domain.GroupBuying.Entities;
using TakeoutSaaS.Domain.Inventory.Entities;
@@ -505,10 +506,16 @@ public sealed class TakeoutAppDbContext(
builder.Property(x => x.ContactEmail).HasMaxLength(128);
builder.Property(x => x.Industry).HasMaxLength(64);
builder.Property(x => x.LogoUrl).HasColumnType("text");
builder.Property(x => x.GeoStatus).HasConversion<int>().HasDefaultValue(GeoLocationStatus.Pending);
builder.Property(x => x.GeoFailReason).HasMaxLength(500);
builder.Property(x => x.GeoRetryCount).HasDefaultValue(0);
builder.Property(x => x.Remarks).HasMaxLength(512);
builder.Property(x => x.OperatingMode).HasConversion<int>();
builder.HasIndex(x => x.Code).IsUnique();
builder.HasIndex(x => x.ContactPhone).IsUnique();
builder.HasIndex(x => new { x.GeoStatus, x.GeoNextRetryAt });
builder.HasIndex(x => new { x.Longitude, x.Latitude })
.HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL");
}
private static void ConfigureTenantVerificationProfile(EntityTypeBuilder<TenantVerificationProfile> builder)
@@ -558,6 +565,9 @@ public sealed class TakeoutAppDbContext(
builder.Property(x => x.Address).HasMaxLength(256);
builder.Property(x => x.ReviewRemarks).HasMaxLength(512);
builder.Property(x => x.OperatingMode).HasConversion<int>();
builder.Property(x => x.GeoStatus).HasConversion<int>().HasDefaultValue(GeoLocationStatus.Pending);
builder.Property(x => x.GeoFailReason).HasMaxLength(500);
builder.Property(x => x.GeoRetryCount).HasDefaultValue(0);
builder.Property(x => x.IsFrozen).HasDefaultValue(false);
builder.Property(x => x.FrozenReason).HasMaxLength(500);
builder.Property(x => x.ClaimedByName).HasMaxLength(100);
@@ -570,6 +580,9 @@ public sealed class TakeoutAppDbContext(
builder.HasIndex(x => x.TenantId);
builder.HasIndex(x => new { x.TenantId, x.Status });
builder.HasIndex(x => x.ClaimedBy);
builder.HasIndex(x => new { x.TenantId, x.GeoStatus, x.GeoNextRetryAt });
builder.HasIndex(x => new { x.Longitude, x.Latitude })
.HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL");
}
private static void ConfigureStore(EntityTypeBuilder<Store> builder)
@@ -599,11 +612,15 @@ public sealed class TakeoutAppDbContext(
builder.Property(x => x.BusinessHours).HasMaxLength(256);
builder.Property(x => x.Announcement).HasMaxLength(512);
builder.Property(x => x.DeliveryRadiusKm).HasPrecision(6, 2);
builder.Property(x => x.GeoStatus).HasConversion<int>().HasDefaultValue(GeoLocationStatus.Pending);
builder.Property(x => x.GeoFailReason).HasMaxLength(500);
builder.Property(x => x.GeoRetryCount).HasDefaultValue(0);
builder.HasIndex(x => new { x.TenantId, x.MerchantId });
builder.HasIndex(x => new { x.TenantId, x.Code }).IsUnique();
builder.HasIndex(x => new { x.TenantId, x.AuditStatus });
builder.HasIndex(x => new { x.TenantId, x.BusinessStatus });
builder.HasIndex(x => new { x.TenantId, x.OwnershipType });
builder.HasIndex(x => new { x.TenantId, x.MerchantId, x.GeoStatus, x.GeoNextRetryAt });
builder.HasIndex(x => new { x.Longitude, x.Latitude })
.HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL");
builder.HasIndex(x => new { x.MerchantId, x.BusinessLicenseNumber })

View File

@@ -0,0 +1,262 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TakeoutSaaS.Infrastructure.Migrations
{
/// <inheritdoc />
public partial class AddGeoLocationRetryMetadata : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "GeoFailReason",
table: "tenants",
type: "character varying(500)",
maxLength: 500,
nullable: true,
comment: "地理定位失败原因。");
migrationBuilder.AddColumn<DateTime>(
name: "GeoNextRetryAt",
table: "tenants",
type: "timestamp with time zone",
nullable: true,
comment: "下次地理定位重试时间UTC。");
migrationBuilder.AddColumn<int>(
name: "GeoRetryCount",
table: "tenants",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "地理定位重试次数。");
migrationBuilder.AddColumn<int>(
name: "GeoStatus",
table: "tenants",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "地理定位状态。");
migrationBuilder.AddColumn<DateTime>(
name: "GeoUpdatedAt",
table: "tenants",
type: "timestamp with time zone",
nullable: true,
comment: "地理定位最近成功时间UTC。");
migrationBuilder.AddColumn<double>(
name: "Latitude",
table: "tenants",
type: "double precision",
nullable: true,
comment: "纬度信息。");
migrationBuilder.AddColumn<double>(
name: "Longitude",
table: "tenants",
type: "double precision",
nullable: true,
comment: "经度信息。");
migrationBuilder.AddColumn<string>(
name: "GeoFailReason",
table: "stores",
type: "character varying(500)",
maxLength: 500,
nullable: true,
comment: "地理定位失败原因。");
migrationBuilder.AddColumn<DateTime>(
name: "GeoNextRetryAt",
table: "stores",
type: "timestamp with time zone",
nullable: true,
comment: "下次地理定位重试时间UTC。");
migrationBuilder.AddColumn<int>(
name: "GeoRetryCount",
table: "stores",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "地理定位重试次数。");
migrationBuilder.AddColumn<int>(
name: "GeoStatus",
table: "stores",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "地理定位状态。");
migrationBuilder.AddColumn<DateTime>(
name: "GeoUpdatedAt",
table: "stores",
type: "timestamp with time zone",
nullable: true,
comment: "地理定位最近成功时间UTC。");
migrationBuilder.AddColumn<string>(
name: "GeoFailReason",
table: "merchants",
type: "character varying(500)",
maxLength: 500,
nullable: true,
comment: "地理定位失败原因。");
migrationBuilder.AddColumn<DateTime>(
name: "GeoNextRetryAt",
table: "merchants",
type: "timestamp with time zone",
nullable: true,
comment: "下次地理定位重试时间UTC。");
migrationBuilder.AddColumn<int>(
name: "GeoRetryCount",
table: "merchants",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "地理定位重试次数。");
migrationBuilder.AddColumn<int>(
name: "GeoStatus",
table: "merchants",
type: "integer",
nullable: false,
defaultValue: 0,
comment: "地理定位状态。");
migrationBuilder.AddColumn<DateTime>(
name: "GeoUpdatedAt",
table: "merchants",
type: "timestamp with time zone",
nullable: true,
comment: "地理定位最近成功时间UTC。");
migrationBuilder.CreateIndex(
name: "IX_tenants_GeoStatus_GeoNextRetryAt",
table: "tenants",
columns: new[] { "GeoStatus", "GeoNextRetryAt" });
migrationBuilder.CreateIndex(
name: "IX_tenants_Longitude_Latitude",
table: "tenants",
columns: new[] { "Longitude", "Latitude" },
filter: "\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_stores_TenantId_MerchantId_GeoStatus_GeoNextRetryAt",
table: "stores",
columns: new[] { "TenantId", "MerchantId", "GeoStatus", "GeoNextRetryAt" });
migrationBuilder.CreateIndex(
name: "IX_merchants_Longitude_Latitude",
table: "merchants",
columns: new[] { "Longitude", "Latitude" },
filter: "\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_merchants_TenantId_GeoStatus_GeoNextRetryAt",
table: "merchants",
columns: new[] { "TenantId", "GeoStatus", "GeoNextRetryAt" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_tenants_GeoStatus_GeoNextRetryAt",
table: "tenants");
migrationBuilder.DropIndex(
name: "IX_tenants_Longitude_Latitude",
table: "tenants");
migrationBuilder.DropIndex(
name: "IX_stores_TenantId_MerchantId_GeoStatus_GeoNextRetryAt",
table: "stores");
migrationBuilder.DropIndex(
name: "IX_merchants_Longitude_Latitude",
table: "merchants");
migrationBuilder.DropIndex(
name: "IX_merchants_TenantId_GeoStatus_GeoNextRetryAt",
table: "merchants");
migrationBuilder.DropColumn(
name: "GeoFailReason",
table: "tenants");
migrationBuilder.DropColumn(
name: "GeoNextRetryAt",
table: "tenants");
migrationBuilder.DropColumn(
name: "GeoRetryCount",
table: "tenants");
migrationBuilder.DropColumn(
name: "GeoStatus",
table: "tenants");
migrationBuilder.DropColumn(
name: "GeoUpdatedAt",
table: "tenants");
migrationBuilder.DropColumn(
name: "Latitude",
table: "tenants");
migrationBuilder.DropColumn(
name: "Longitude",
table: "tenants");
migrationBuilder.DropColumn(
name: "GeoFailReason",
table: "stores");
migrationBuilder.DropColumn(
name: "GeoNextRetryAt",
table: "stores");
migrationBuilder.DropColumn(
name: "GeoRetryCount",
table: "stores");
migrationBuilder.DropColumn(
name: "GeoStatus",
table: "stores");
migrationBuilder.DropColumn(
name: "GeoUpdatedAt",
table: "stores");
migrationBuilder.DropColumn(
name: "GeoFailReason",
table: "merchants");
migrationBuilder.DropColumn(
name: "GeoNextRetryAt",
table: "merchants");
migrationBuilder.DropColumn(
name: "GeoRetryCount",
table: "merchants");
migrationBuilder.DropColumn(
name: "GeoStatus",
table: "merchants");
migrationBuilder.DropColumn(
name: "GeoUpdatedAt",
table: "merchants");
}
}
}

View File

@@ -2468,6 +2468,31 @@ namespace TakeoutSaaS.Infrastructure.Migrations
.HasColumnType("character varying(500)")
.HasComment("冻结原因。");
b.Property<string>("GeoFailReason")
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasComment("地理定位失败原因。");
b.Property<DateTime?>("GeoNextRetryAt")
.HasColumnType("timestamp with time zone")
.HasComment("下次地理定位重试时间UTC。");
b.Property<int>("GeoRetryCount")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasComment("地理定位重试次数。");
b.Property<int>("GeoStatus")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasComment("地理定位状态。");
b.Property<DateTime?>("GeoUpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("地理定位最近成功时间UTC。");
b.Property<bool>("IsFrozen")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
@@ -2557,8 +2582,13 @@ namespace TakeoutSaaS.Infrastructure.Migrations
b.HasIndex("TenantId");
b.HasIndex("Longitude", "Latitude")
.HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL");
b.HasIndex("TenantId", "Status");
b.HasIndex("TenantId", "GeoStatus", "GeoNextRetryAt");
b.ToTable("merchants", null, t =>
{
t.HasComment("商户主体信息,承载入驻和资质审核结果。");
@@ -4935,6 +4965,31 @@ namespace TakeoutSaaS.Infrastructure.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("强制关闭时间。");
b.Property<string>("GeoFailReason")
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasComment("地理定位失败原因。");
b.Property<DateTime?>("GeoNextRetryAt")
.HasColumnType("timestamp with time zone")
.HasComment("下次地理定位重试时间UTC。");
b.Property<int>("GeoRetryCount")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasComment("地理定位重试次数。");
b.Property<int>("GeoStatus")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasComment("地理定位状态。");
b.Property<DateTime?>("GeoUpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("地理定位最近成功时间UTC。");
b.Property<double?>("Latitude")
.HasColumnType("double precision")
.HasComment("纬度。");
@@ -5056,6 +5111,8 @@ namespace TakeoutSaaS.Infrastructure.Migrations
b.HasIndex("TenantId", "OwnershipType");
b.HasIndex("TenantId", "MerchantId", "GeoStatus", "GeoNextRetryAt");
b.ToTable("stores", null, t =>
{
t.HasComment("门店信息,承载营业配置与能力。");
@@ -6460,11 +6517,40 @@ namespace TakeoutSaaS.Infrastructure.Migrations
.HasColumnType("timestamp with time zone")
.HasComment("服务到期时间UTC。");
b.Property<string>("GeoFailReason")
.HasMaxLength(500)
.HasColumnType("character varying(500)")
.HasComment("地理定位失败原因。");
b.Property<DateTime?>("GeoNextRetryAt")
.HasColumnType("timestamp with time zone")
.HasComment("下次地理定位重试时间UTC。");
b.Property<int>("GeoRetryCount")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasComment("地理定位重试次数。");
b.Property<int>("GeoStatus")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasDefaultValue(0)
.HasComment("地理定位状态。");
b.Property<DateTime?>("GeoUpdatedAt")
.HasColumnType("timestamp with time zone")
.HasComment("地理定位最近成功时间UTC。");
b.Property<string>("Industry")
.HasMaxLength(64)
.HasColumnType("character varying(64)")
.HasComment("所属行业,如餐饮、零售等。");
b.Property<double?>("Latitude")
.HasColumnType("double precision")
.HasComment("纬度信息。");
b.Property<string>("LegalEntityName")
.HasColumnType("text")
.HasComment("法人或公司主体名称。");
@@ -6473,6 +6559,10 @@ namespace TakeoutSaaS.Infrastructure.Migrations
.HasColumnType("text")
.HasComment("LOGO 图片地址。");
b.Property<double?>("Longitude")
.HasColumnType("double precision")
.HasComment("经度信息。");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128)
@@ -6537,6 +6627,11 @@ namespace TakeoutSaaS.Infrastructure.Migrations
b.HasIndex("ContactPhone")
.IsUnique();
b.HasIndex("GeoStatus", "GeoNextRetryAt");
b.HasIndex("Longitude", "Latitude")
.HasFilter("\"Longitude\" IS NOT NULL AND \"Latitude\" IS NOT NULL");
b.ToTable("tenants", null, t =>
{
t.HasComment("租户信息,描述租户的生命周期与基础资料。");