fix(promotion-import): 实例(一)(二)多备选源路径;目录内唯一 mov/jpg 自动匹配

Made-with: Cursor
This commit is contained in:
whm
2026-03-21 21:47:06 +08:00
parent 1710a11dad
commit 6d049fe0e8
4 changed files with 141 additions and 56 deletions

View File

@@ -6,26 +6,40 @@ $Src = Join-Path $Root "web\promotion\视频发布"
$Dst = Join-Path $Root "web\promotion\social" $Dst = Join-Path $Root "web\promotion\social"
New-Item -ItemType Directory -Force -Path $Dst | Out-Null New-Item -ItemType Directory -Force -Path $Dst | Out-Null
function Copy-IfExists($fromRel, $toName) { function Copy-First($toName, [string[]]$fromRels) {
$from = Join-Path $Src $fromRel
$to = Join-Path $Dst $toName $to = Join-Path $Dst $toName
foreach ($rel in $fromRels) {
$from = Join-Path $Src $rel
if (Test-Path -LiteralPath $from) { if (Test-Path -LiteralPath $from) {
Copy-Item -LiteralPath $from -Destination $to -Force Copy-Item -LiteralPath $from -Destination $to -Force
Write-Host "OK $toName" Write-Host "OK $toName <= $rel"
} else { return
Write-Warning "SKIP (缺失): $from"
} }
} }
Write-Warning "SKIP (均未找到): -> $toName"
}
Copy-IfExists "宇恒一号操作计算软件实例(一)\宣传片-封面.jpg" "video-calc-demo-1-cover.jpg" Copy-First "video-calc-demo-1-cover.jpg" @(
Copy-IfExists "宇恒一号操作计算软件实例(一)\宣传片.mov" "video-calc-demo-1.mov" "宇恒一号操作计算软件实例(一)\宣传片-封面.jpg",
Copy-IfExists "宇恒一号操作计算软件实例(\宇恒一号操作计算软件实例(-封面.jpg" "video-calc-demo-2-cover.jpg" "宇恒一号操作计算软件实例(\宇恒一号操作计算软件实例(-封面.jpg"
Copy-IfExists "宇恒一号操作计算软件实例(二)\宇恒一号操作计算软件实例(二).mov" "video-calc-demo-2.mov" )
Copy-IfExists "宇恒一号AIWord简介\宇恒一号AIWord简介-封面.jpg" "video-aiword-cover.jpg" Copy-First "video-calc-demo-1.mov" @(
Copy-IfExists "宇恒一号AIWord简介\宇恒一号AIWord简介.mov" "video-aiword.mov" "宇恒一号操作计算软件实例(一)\宣传片.mov",
Copy-IfExists "宇恒一号语音办公实例\宇恒一号语音办公实例-封面.jpg" "video-voice-office-cover.jpg" "宇恒一号操作计算软件实例(一)\宇恒一号操作计算软件实例(一).mov"
Copy-IfExists "宇恒一号语音办公实例\宇恒一号语音办公实例.mov" "video-voice-office.mov" )
Copy-IfExists "宇恒一号AI 全自动办发票\宇恒一号AI 全自动办发票-封面.jpg" "video-invoice-ai-cover.jpg" Copy-First "video-calc-demo-2-cover.jpg" @(
Copy-IfExists "宇恒一号AI 全自动办发票\宇恒一号AI 全自动办发票.mov" "video-invoice-ai.mov" "宇恒一号操作计算软件实例(二)\宇恒一号操作计算软件实例(二)-封面.jpg",
"宇恒一号操作计算软件实例(二)\宣传片-封面.jpg"
)
Copy-First "video-calc-demo-2.mov" @(
"宇恒一号操作计算软件实例(二)\宇恒一号操作计算软件实例(二).mov",
"宇恒一号操作计算软件实例(二)\宣传片.mov"
)
Copy-First "video-aiword-cover.jpg" @("宇恒一号AIWord简介\宇恒一号AIWord简介-封面.jpg")
Copy-First "video-aiword.mov" @("宇恒一号AIWord简介\宇恒一号AIWord简介.mov")
Copy-First "video-voice-office-cover.jpg" @("宇恒一号语音办公实例\宇恒一号语音办公实例-封面.jpg")
Copy-First "video-voice-office.mov" @("宇恒一号语音办公实例\宇恒一号语音办公实例.mov")
Copy-First "video-invoice-ai-cover.jpg" @("宇恒一号AI 全自动办发票\宇恒一号AI 全自动办发票-封面.jpg")
Copy-First "video-invoice-ai.mov" @("宇恒一号AI 全自动办发票\宇恒一号AI 全自动办发票.mov")
Write-Host "完成。Linux 服务器上建议在 social 目录执行: chmod -R a+rX ." Write-Host "完成。Linux 服务器上建议在 social 目录执行: chmod -R a+rX ."

View File

@@ -8,35 +8,48 @@ SRC="$ROOT/web/promotion/视频发布"
DST="$ROOT/web/promotion/social" DST="$ROOT/web/promotion/social"
mkdir -p "$DST" mkdir -p "$DST"
copy_if () { # 按顺序使用第一个存在的源文件
local from="$1" to="$2" copy_first() {
local dest="$1"
shift
for from in "$@"; do
if [[ -f "$from" ]]; then if [[ -f "$from" ]]; then
cp -f "$from" "$to" cp -f "$from" "$dest"
echo "OK $to" echo "OK $(basename "$dest") <= $from"
else return 0
echo "SKIP (缺失): $from" >&2
fi fi
done
echo "SKIP (均未找到): -> $dest" >&2
return 1
} }
# 操作与计算软件实例(一) # 操作与计算软件实例(一)
copy_if "$SRC/宇恒一号操作计算软件实例(一)/宣传片-封面.jpg" "$DST/video-calc-demo-1-cover.jpg" copy_first "$DST/video-calc-demo-1-cover.jpg" \
copy_if "$SRC/宇恒一号操作计算软件实例(一)/宣传片.mov" "$DST/video-calc-demo-1.mov" "$SRC/宇恒一号操作计算软件实例(一)/宣传片-封面.jpg" \
"$SRC/宇恒一号操作计算软件实例(一)/宇恒一号操作计算软件实例(一)-封面.jpg" || true
copy_first "$DST/video-calc-demo-1.mov" \
"$SRC/宇恒一号操作计算软件实例(一)/宣传片.mov" \
"$SRC/宇恒一号操作计算软件实例(一)/宇恒一号操作计算软件实例(一).mov" || true
# 操作与计算软件实例(二) # 操作与计算软件实例(二)
copy_if "$SRC/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二)-封面.jpg" "$DST/video-calc-demo-2-cover.jpg" copy_first "$DST/video-calc-demo-2-cover.jpg" \
copy_if "$SRC/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二).mov" "$DST/video-calc-demo-2.mov" "$SRC/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二)-封面.jpg" \
"$SRC/宇恒一号操作计算软件实例(二)/宣传片-封面.jpg" || true
copy_first "$DST/video-calc-demo-2.mov" \
"$SRC/宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二).mov" \
"$SRC/宇恒一号操作计算软件实例(二)/宣传片.mov" || true
# AI Word # AI Word
copy_if "$SRC/宇恒一号AIWord简介/宇恒一号AIWord简介-封面.jpg" "$DST/video-aiword-cover.jpg" copy_first "$DST/video-aiword-cover.jpg" "$SRC/宇恒一号AIWord简介/宇恒一号AIWord简介-封面.jpg" || true
copy_if "$SRC/宇恒一号AIWord简介/宇恒一号AIWord简介.mov" "$DST/video-aiword.mov" copy_first "$DST/video-aiword.mov" "$SRC/宇恒一号AIWord简介/宇恒一号AIWord简介.mov" || true
# 语音办公 # 语音办公
copy_if "$SRC/宇恒一号语音办公实例/宇恒一号语音办公实例-封面.jpg" "$DST/video-voice-office-cover.jpg" copy_first "$DST/video-voice-office-cover.jpg" "$SRC/宇恒一号语音办公实例/宇恒一号语音办公实例-封面.jpg" || true
copy_if "$SRC/宇恒一号语音办公实例/宇恒一号语音办公实例.mov" "$DST/video-voice-office.mov" copy_first "$DST/video-voice-office.mov" "$SRC/宇恒一号语音办公实例/宇恒一号语音办公实例.mov" || true
# 办发票(目录名含全角逗号) # 办发票(目录名含全角逗号)
copy_if "$SRC/宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票-封面.jpg" "$DST/video-invoice-ai-cover.jpg" copy_first "$DST/video-invoice-ai-cover.jpg" "$SRC/宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票-封面.jpg" || true
copy_if "$SRC/宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票.mov" "$DST/video-invoice-ai.mov" copy_first "$DST/video-invoice-ai.mov" "$SRC/宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票.mov" || true
if command -v chmod >/dev/null 2>&1; then if command -v chmod >/dev/null 2>&1; then
chmod -R a+rX "$DST" 2>/dev/null || true chmod -R a+rX "$DST" 2>/dev/null || true

View File

@@ -2,6 +2,8 @@
`web/promotion/视频发布/` 下映射表中的文件复制到 **`{upload}/sites/{site_id}/promotion/social/`**,并在 **`site_assets`** 集合插入记录(与后台「保留原文件名」上传到 `promotion/social` 一致)。 `web/promotion/视频发布/` 下映射表中的文件复制到 **`{upload}/sites/{site_id}/promotion/social/`**,并在 **`site_assets`** 集合插入记录(与后台「保留原文件名」上传到 `promotion/social` 一致)。
对「操作与计算(一)(二)」等条目会**按顺序尝试多个源文件名**;若仍找不到且子目录内**恰好只有一个** `.mov``.jpg`,会自动选用(解决「有些视频有、有些 404」多为源文件名与映射不一致
## 参数 ## 参数
| 参数 | 说明 | | 参数 | 说明 |

View File

@@ -57,21 +57,77 @@ func mimeForExt(ext string) string {
} }
} }
// 与 scripts/sync-video-assets-to-social.sh 一致:源相对「视频发布」目录,目标为 promotion/social 下英文名 // importRule按顺序尝试 SrcRels若均不存在且 FallbackScanDir 非空,则在该子目录下「仅一个 .mov/.jpg 时」自动选用(兼容实际文件夹命名与文件名不一致)
var mappings = []struct { type importRule struct {
SrcRel string // 相对 视频发布/ SrcRels []string
Dst string // 仅文件名,落在 promotion/social/ Dst string
}{ FallbackScanDir string // 相对 视频发布/,仅当目标为视频时用 .mov封面用 .jpg
{"宇恒一号操作计算软件实例(一)/宣传片-封面.jpg", "video-calc-demo-1-cover.jpg"}, }
{"宇恒一号操作计算软件实例(一)/宣传片.mov", "video-calc-demo-1.mov"},
{"宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二)-封面.jpg", "video-calc-demo-2-cover.jpg"}, // 与 sync-video-assets-to-social.sh 对齐,并增加备选路径(线上常见「实例(一)」内不叫宣传片.mov 的情况)
{"宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二).mov", "video-calc-demo-2.mov"}, var mappings = []importRule{
{"宇恒一号AIWord简介/宇恒一号AIWord简介-封面.jpg", "video-aiword-cover.jpg"}, {[]string{
{"宇恒一号AIWord简介/宇恒一号AIWord简介.mov", "video-aiword.mov"}, "宇恒一号操作计算软件实例(一)/宣传片-封面.jpg",
{"宇恒一号语音办公实例/宇恒一号语音办公实例-封面.jpg", "video-voice-office-cover.jpg"}, "宇恒一号操作计算软件实例(一)/宇恒一号操作计算软件实例(一)-封面.jpg",
{"宇恒一号语音办公实例/宇恒一号语音办公实例.mov", "video-voice-office.mov"}, }, "video-calc-demo-1-cover.jpg", "宇恒一号操作计算软件实例(一)"},
{"宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票-封面.jpg", "video-invoice-ai-cover.jpg"}, {[]string{
{"宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票.mov", "video-invoice-ai.mov"}, "宇恒一号操作计算软件实例(一)/宣传片.mov",
"宇恒一号操作计算软件实例(一)/宇恒一号操作计算软件实例(一).mov",
}, "video-calc-demo-1.mov", "宇恒一号操作计算软件实例(一)"},
{[]string{
"宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二)-封面.jpg",
"宇恒一号操作计算软件实例(二)/宣传片-封面.jpg",
}, "video-calc-demo-2-cover.jpg", "宇恒一号操作计算软件实例(二)"},
{[]string{
"宇恒一号操作计算软件实例(二)/宇恒一号操作计算软件实例(二).mov",
"宇恒一号操作计算软件实例(二)/宣传片.mov",
}, "video-calc-demo-2.mov", "宇恒一号操作计算软件实例(二)"},
{[]string{"宇恒一号AIWord简介/宇恒一号AIWord简介-封面.jpg"}, "video-aiword-cover.jpg", "宇恒一号AIWord简介"},
{[]string{"宇恒一号AIWord简介/宇恒一号AIWord简介.mov"}, "video-aiword.mov", "宇恒一号AIWord简介"},
{[]string{"宇恒一号语音办公实例/宇恒一号语音办公实例-封面.jpg"}, "video-voice-office-cover.jpg", "宇恒一号语音办公实例"},
{[]string{"宇恒一号语音办公实例/宇恒一号语音办公实例.mov"}, "video-voice-office.mov", "宇恒一号语音办公实例"},
{[]string{"宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票-封面.jpg"}, "video-invoice-ai-cover.jpg", "宇恒一号AI 全自动办发票"},
{[]string{"宇恒一号AI 全自动办发票/宇恒一号AI 全自动办发票.mov"}, "video-invoice-ai.mov", "宇恒一号AI 全自动办发票"},
}
func resolveSourceFile(videoPublish string, rule importRule) (absPath, relChosen string, ok bool) {
for _, rel := range rule.SrcRels {
p := filepath.Join(videoPublish, filepath.FromSlash(rel))
if st, err := os.Stat(p); err == nil && !st.IsDir() {
return p, rel, true
}
}
if rule.FallbackScanDir == "" {
return "", "", false
}
ext := strings.ToLower(filepath.Ext(rule.Dst))
if ext != ".mov" && ext != ".jpg" && ext != ".jpeg" {
return "", "", false
}
dir := filepath.Join(videoPublish, filepath.FromSlash(rule.FallbackScanDir))
entries, err := os.ReadDir(dir)
if err != nil {
return "", "", false
}
var matches []string
for _, e := range entries {
if e.IsDir() {
continue
}
ne := strings.ToLower(filepath.Ext(e.Name()))
if ext == ".mov" && ne == ".mov" {
matches = append(matches, e.Name())
}
if (ext == ".jpg" || ext == ".jpeg") && (ne == ".jpg" || ne == ".jpeg") {
matches = append(matches, e.Name())
}
}
if len(matches) != 1 {
return "", "", false
}
rel := filepath.ToSlash(filepath.Join(rule.FallbackScanDir, matches[0]))
p := filepath.Join(videoPublish, filepath.FromSlash(rel))
return p, rel, true
} }
func main() { func main() {
@@ -144,9 +200,9 @@ func main() {
ok, skip, fail := 0, 0, 0 ok, skip, fail := 0, 0, 0
for _, m := range mappings { for _, m := range mappings {
from := filepath.Join(videoPublish, filepath.FromSlash(m.SrcRel)) from, srcRelUsed, found := resolveSourceFile(videoPublish, m)
if _, err := os.Stat(from); err != nil { if !found {
log.Printf("SKIP 源文件不存在: %s", from) log.Printf("SKIP 源文件不存在(已试备选路径): dst=%s dir=%s", m.Dst, m.FallbackScanDir)
skip++ skip++
continue continue
} }
@@ -168,7 +224,7 @@ func main() {
} }
if err := copyFile(from, destPath); err != nil { if err := copyFile(from, destPath); err != nil {
log.Printf("FAIL 复制 %s: %v", m.SrcRel, err) log.Printf("FAIL 复制 %s: %v", srcRelUsed, err)
fail++ fail++
continue continue
} }
@@ -193,7 +249,7 @@ func main() {
"downloadable": false, "downloadable": false,
"created_at": time.Now().Format(time.RFC3339), "created_at": time.Now().Format(time.RFC3339),
"import_source": "video_publish_legacy", "import_source": "video_publish_legacy",
"source_relpath": m.SrcRel, "source_relpath": srcRelUsed,
"promotion_alias": filepath.ToSlash(filepath.Join("promotion", "social", m.Dst)), "promotion_alias": filepath.ToSlash(filepath.Join("promotion", "social", m.Dst)),
} }
if _, err := coll.InsertOne(ctx, doc); err != nil { if _, err := coll.InsertOne(ctx, doc); err != nil {
@@ -201,7 +257,7 @@ func main() {
fail++ fail++
continue continue
} }
log.Printf("OK %s -> %s", m.SrcRel, relPath) log.Printf("OK %s -> %s", srcRelUsed, relPath)
ok++ ok++
} }