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
+34 -13
View File
@@ -189,6 +189,7 @@ type MeijiequanPublishedArticle struct {
Title string
ResourceID string
ResourceName string
PriceCents int64
ExternalArticleURL string
Status string
RejectReason string
@@ -1600,13 +1601,18 @@ func parseMeijiequanPublishedArticleMap(item map[string]any) (MeijiequanPublishe
if title == "" && articleURL == "" {
return MeijiequanPublishedArticle{}, false
}
orderCode := strings.TrimSpace(meijiequanAnyString(firstMapValue(item, "order_code", "code", "sn", "order_sn", "dingdanhao", "order_no", "orderno", "danhao")))
if !isLikelyMeijiequanOrderCode(orderCode) {
orderCode = firstMeijiequanOrderCode(orderCode)
}
raw, _ := json.Marshal(item)
return MeijiequanPublishedArticle{
OrderID: strings.TrimSpace(meijiequanAnyString(firstMapValue(item, "id", "order_id", "article_id"))),
OrderCode: strings.TrimSpace(meijiequanAnyString(firstMapValue(item, "order_code", "code", "sn", "order_sn", "dingdanhao", "order_no", "orderno", "danhao"))),
OrderCode: orderCode,
Title: title,
ResourceID: normalizeMeijiequanPublishedResourceID(meijiequanAnyString(firstMapValue(item, "resource_id", "media_id", "uid", "mid"))),
ResourceName: strings.TrimSpace(meijiequanAnyString(firstMapValue(item, "meiti", "media_name", "resource_name", "name"))),
PriceCents: firstMeijiequanArticlePriceCents(item),
ExternalArticleURL: articleURL,
Status: strings.TrimSpace(meijiequanAnyString(firstMapValue(item, "status", "status_text", "state"))),
RejectReason: strings.TrimSpace(meijiequanAnyString(firstMapValue(item, "reject_reason", "reason", "tuigao_reason", "tuigaoyuanyin", "refund_reason", "error_message"))),
@@ -1732,6 +1738,7 @@ func parseMeijiequanPublishedArticleHTMLRow(row *nethtml.Node, cells []*nethtml.
if status == "" {
status = extractMeijiequanPublishedStatus(rowText)
}
priceCents, _ := parsePriceCents(columnValues["价格"])
rejectReason := strings.TrimSpace(columnValues["退稿原因"])
return MeijiequanPublishedArticle{
OrderID: strings.TrimSpace(firstMeijiequanHTMLAttr(row, "data-id", "data-order-id", "data-article-id")),
@@ -1739,6 +1746,7 @@ func parseMeijiequanPublishedArticleHTMLRow(row *nethtml.Node, cells []*nethtml.
Title: strings.TrimSpace(title),
ResourceID: resourceID,
ResourceName: resourceName,
PriceCents: priceCents,
ExternalArticleURL: articleURL,
Status: status,
RejectReason: rejectReason,
@@ -1814,6 +1822,8 @@ func normalizeMeijiequanHeaderName(value string) string {
return "订单号"
case strings.Contains(value, "资源名称"):
return "资源名称"
case strings.Contains(value, "价格") || strings.Contains(value, "金额"):
return "价格"
case strings.Contains(value, "退稿原因"):
return "退稿原因"
case strings.Contains(value, "状态"):
@@ -2091,7 +2101,10 @@ func extractMeijiequanOrderCodeFromText(value string) string {
for idx, field := range fields {
if strings.Contains(field, "单号") || strings.Contains(strings.ToLower(field), "order") {
if idx+1 < len(fields) {
return strings.Trim(fields[idx+1], " :,;")
token := strings.Trim(fields[idx+1], " :,;")
if isLikelyMeijiequanOrderCode(token) {
return token
}
}
}
}
@@ -2115,25 +2128,21 @@ func extractMeijiequanOrderCodeFromTexts(texts []string) string {
func isLikelyMeijiequanOrderCode(value string) bool {
value = strings.ToUpper(strings.TrimSpace(value))
if len(value) < 4 || len(value) > 64 {
if len(value) < 5 || len(value) > 64 {
return false
}
if strings.Contains(value, "HTTP") || strings.Contains(value, "://") {
return false
}
hasDigit := false
hasLetter := false
for _, r := range value {
switch {
case r >= '0' && r <= '9':
hasDigit = true
case r >= 'A' && r <= 'Z':
hasLetter = true
default:
if !strings.HasPrefix(value, "WM") {
return false
}
for _, r := range strings.TrimPrefix(value, "WM") {
if r < '0' || r > '9' {
return false
}
}
return hasDigit && hasLetter
return true
}
func extractMeijiequanResourceNameFromTexts(texts []string) string {
@@ -2469,6 +2478,18 @@ func extractMeijiequanPrices(item map[string]any) map[string]int64 {
return out
}
func firstMeijiequanArticlePriceCents(item map[string]any) int64 {
for _, key := range []string{
"price", "sale_price", "cost_price", "amount", "money", "jine", "jiage",
"media_price", "resource_price", "order_price", "total_price",
} {
if cents, ok := parsePriceCents(item[key]); ok {
return cents
}
}
return 0
}
func parsePriceCents(value any) (int64, bool) {
switch typed := value.(type) {
case nil: