feat(media-supply): resource cache sync and order flow refinements
Frontend CI / Frontend (push) Successful in 3m38s
Backend CI / Backend (push) Failing after 26m59s

Add a resource cache sync entrypoint (RunMediaResourceSyncOnce) driven
by the new scheduler job, and rework the backlink/sync worker around it.
Tune the meijiequan client and slow the default backlink sync interval
from 10m to 30m to reduce upstream pressure.

Trim unused public/transport surface (handler + router + swagger) and
simplify admin-web order management and ops-web media-supply views,
dropping stale shared-types fields.
This commit is contained in:
2026-06-02 14:50:36 +08:00
parent 842782b3dd
commit 723c3ffb86
14 changed files with 529 additions and 208 deletions
+157 -5
View File
@@ -104,6 +104,28 @@ func TestMediaSupplySellPriceAppliesMarkupBeforeRoundUp(t *testing.T) {
}
}
func TestMediaSupplyOverrideBelowCostUsesSyncedCostPrices(t *testing.T) {
resource := SupplierMediaResource{
CostPriceCents: 1000,
CostPrices: map[string]int64{
"price": 1500,
"priceyc": 2200,
},
}
if !mediaSupplyOverrideBelowCost(resource, "price", 1400, true) {
t.Fatal("expected default price override below synced price cost to be cleared")
}
if mediaSupplyOverrideBelowCost(resource, "price", 1500, true) {
t.Fatal("expected override equal to synced price cost to stay")
}
if !mediaSupplyOverrideBelowCost(resource, "priceyc", 2100, true) {
t.Fatal("expected priceyc override below synced priceyc cost to be cleared")
}
if mediaSupplyOverrideBelowCost(resource, "priceyc", 2100, false) {
t.Fatal("disabled override should not be treated as active loss risk")
}
}
func TestMediaSupplyResourceOrderSQL(t *testing.T) {
priceSQL := "sell_price_expr"
if got := mediaSupplyResourceOrderSQL("price", priceSQL); got != "(sell_price_expr) ASC, r.id DESC" {
@@ -149,9 +171,23 @@ func TestRetryableMediaSupplyErrorTreatsBusinessFailuresAsTerminal(t *testing.T)
}
}
func TestMediaSupplyRefreshFailureMessageIsActionable(t *testing.T) {
got := mediaSupplyPublicErrorMessage(errMediaSupplySelectedResourcesMissing)
if got != "媒体资源暂不可用或已下架,请重新选择媒体" {
t.Fatalf("unexpected refresh failure message: %s", got)
}
got = mediaSupplyPublicGenericErrorMessage("failed to refresh supplier price for 1 selected resources")
if got != "媒体资源暂不可用或已下架,请重新选择媒体" {
t.Fatalf("unexpected generic refresh failure message: %s", got)
}
}
func TestScanMediaSupplyOrderRedactsPublicFields(t *testing.T) {
now := time.Now().UTC()
errorMessage := "failed to refresh supplier price for 1 selected resources"
externalOrderID := "U6210"
externalOrderCode := "U6210"
item, err := scanMediaSupplyOrder(mediaSupplyOrderScanStub{values: []any{
int64(1),
int64(2),
@@ -165,8 +201,8 @@ func TestScanMediaSupplyOrderRedactsPublicFields(t *testing.T) {
"测试标题",
(*string)(nil),
(*string)(nil),
(*string)(nil),
(*string)(nil),
&externalOrderID,
&externalOrderCode,
(*string)(nil),
int64(100),
int64(120),
@@ -187,9 +223,15 @@ func TestScanMediaSupplyOrderRedactsPublicFields(t *testing.T) {
if strings.Contains(strings.ToLower(item.Supplier), "meijiequan") {
t.Fatalf("supplier was not redacted: %s", item.Supplier)
}
if item.ErrorMessage == nil || *item.ErrorMessage != "媒体资源价格刷新失败,请稍后重试或重新选择媒体" {
if item.ErrorMessage == nil || *item.ErrorMessage != "媒体资源暂不可用或已下架,请重新选择媒体" {
t.Fatalf("unexpected public error message: %#v", item.ErrorMessage)
}
if item.ExternalOrderID != nil {
t.Fatalf("external order id should not be exposed: %#v", item.ExternalOrderID)
}
if item.ExternalOrderCode != nil {
t.Fatalf("invalid external order code should not be exposed: %#v", item.ExternalOrderCode)
}
}
func TestMeijiequanCreateOrderFormMatchesUpstreamShape(t *testing.T) {
@@ -471,6 +513,7 @@ func TestDecodeMeijiequanAllArticleHTMLKeepsStatusAndRejectReasonColumns(t *test
if problem.OrderCode != "WM119065" ||
problem.Title != "合肥全屋定制怎么选?5 大主流品牌优缺点对比 + 选购建议" ||
problem.ResourceName != "博客园(GEO排名可发)" ||
problem.PriceCents != 300 ||
problem.Status != "退稿申请中" ||
problem.RejectReason != "不想发布了" {
t.Fatalf("unexpected problem article: %#v", problem)
@@ -478,6 +521,7 @@ func TestDecodeMeijiequanAllArticleHTMLKeepsStatusAndRejectReasonColumns(t *test
published := page.Items[1]
if published.OrderCode != "WM118996" ||
published.ResourceName != "金融树洞网" ||
published.PriceCents != 300 ||
published.Status != "已发布" ||
published.ExternalArticleURL != "http://jrsd.huaihuarexian.cn/caijing/11221014.html" {
t.Fatalf("unexpected published article: %#v", published)
@@ -507,14 +551,15 @@ func TestDecodeMeijiequanPublishedArticleJSONRejectsShowURL(t *testing.T) {
func TestMatchMeijiequanPublishedArticlesPrefersResourceID(t *testing.T) {
items := []MediaSupplyOrderItem{
{ID: 11, SupplierResourceID: "36122", ResourceNameSnapshot: "测试新闻网"},
{ID: 12, SupplierResourceID: "36123", ResourceNameSnapshot: "另一个媒体"},
{ID: 11, SupplierResourceID: "36122", ResourceNameSnapshot: "测试新闻网", LockedCostPriceCents: 300, LockedSellPriceCents: 500},
{ID: 12, SupplierResourceID: "36123", ResourceNameSnapshot: "另一个媒体", LockedCostPriceCents: 300, LockedSellPriceCents: 500},
}
matches := matchMeijiequanPublishedArticles(mediaSupplySubmitOrder{Title: "2026合肥品牌推荐"}, items, []MeijiequanPublishedArticle{
{
Title: "2026合肥品牌推荐",
ResourceID: "36123_price_0",
ResourceName: "另一个媒体",
PriceCents: 300,
ExternalArticleURL: "https://news.example.test/b.html",
},
})
@@ -526,6 +571,76 @@ func TestMatchMeijiequanPublishedArticlesPrefersResourceID(t *testing.T) {
}
}
func TestMatchMeijiequanOrderCodeArticleUsesTitleResourceAndPriceFallback(t *testing.T) {
items := []MediaSupplyOrderItem{{
ID: 11,
SupplierResourceID: "36122",
ResourceNameSnapshot: "博客园(GEO排名可发)",
LockedCostPriceCents: 300,
LockedSellPriceCents: 900,
}}
article := matchMeijiequanOrderCodeArticle(
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选"},
items,
[]MeijiequanPublishedArticle{
{OrderCode: "WM120001", Title: "合肥全屋定制怎么选", ResourceName: "博客园(GEO排名可发)", PriceCents: 900},
{OrderCode: "WM120002", Title: "合肥全屋定制怎么选", ResourceName: "博客园(GEO排名可发)", PriceCents: 300},
},
)
if article == nil || article.OrderCode != "WM120002" {
t.Fatalf("expected title/resource/price fallback to match WM120002, got %#v", article)
}
}
func TestMatchMeijiequanOrderCodeArticleDoesNotFallbackWhenOrderCodeDiffers(t *testing.T) {
orderCode := "WM120090"
items := []MediaSupplyOrderItem{{
ID: 11,
SupplierResourceID: "36122",
ResourceNameSnapshot: "博客园(GEO排名可发)",
LockedCostPriceCents: 300,
LockedSellPriceCents: 900,
}}
article := matchMeijiequanOrderCodeArticle(
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选", ExternalOrderCode: &orderCode},
items,
[]MeijiequanPublishedArticle{{
OrderCode: "WM120091",
Title: "合肥全屋定制怎么选",
ResourceName: "博客园(GEO排名可发)",
PriceCents: 300,
}},
)
if article != nil {
t.Fatalf("article with different order code should not match: %#v", article)
}
}
func TestMatchMeijiequanPublishedArticlesDoesNotFallbackWhenOrderCodeDiffers(t *testing.T) {
orderCode := "WM120090"
items := []MediaSupplyOrderItem{{
ID: 11,
SupplierResourceID: "36122",
ResourceNameSnapshot: "博客园(GEO排名可发)",
LockedCostPriceCents: 300,
LockedSellPriceCents: 900,
}}
matches := matchMeijiequanPublishedArticles(
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选", ExternalOrderCode: &orderCode},
items,
[]MeijiequanPublishedArticle{{
OrderCode: "WM120091",
Title: "合肥全屋定制怎么选",
ResourceName: "博客园(GEO排名可发)",
PriceCents: 300,
ExternalArticleURL: "https://news.example.test/a.html",
}},
)
if len(matches) != 0 {
t.Fatalf("article with different order code should not match: %#v", matches)
}
}
func TestMatchMeijiequanProblemArticleReturnsRejectReasonForCustomer(t *testing.T) {
orderCode := "WM119065"
article := matchMeijiequanProblemArticle(
@@ -547,6 +662,43 @@ func TestMatchMeijiequanProblemArticleReturnsRejectReasonForCustomer(t *testing.
}
}
func TestMatchMeijiequanProblemArticleDoesNotFallbackWhenOrderCodeDiffers(t *testing.T) {
orderCode := "WM119065"
article := matchMeijiequanProblemArticle(
mediaSupplySubmitOrder{Title: "合肥全屋定制怎么选", ExternalOrderCode: &orderCode},
[]MediaSupplyOrderItem{{
ID: 11,
SupplierResourceID: "36122",
ResourceNameSnapshot: "博客园(GEO排名可发)",
LockedCostPriceCents: 300,
}},
[]MeijiequanPublishedArticle{{
OrderCode: "WM119066",
Title: "合肥全屋定制怎么选",
ResourceName: "博客园(GEO排名可发)",
PriceCents: 300,
Status: "退稿申请中",
RejectReason: "不想发布了",
}},
)
if article != nil {
t.Fatalf("problem article with different order code should not match: %#v", article)
}
}
func TestMeijiequanOrderCodeOnlyAcceptsWMOrderNumber(t *testing.T) {
if isLikelyMeijiequanOrderCode("U6210") {
t.Fatal("U6210 should not be treated as an external order number")
}
if got := firstMeijiequanOrderCode("user U6210 created WM120090"); got != "WM120090" {
t.Fatalf("unexpected order code extraction: %s", got)
}
invalidCode := "U6210"
if got := mediaSupplyOrderCodeValue(mediaSupplySubmitOrder{ExternalOrderCode: &invalidCode}); got != "" {
t.Fatalf("invalid external order code should not be used for matching: %s", got)
}
}
func TestMaskMeijiequanUsername(t *testing.T) {
if got := maskMeijiequanUsername("17788409108"); got != "177******08" {
t.Fatalf("unexpected masked username: %s", got)