#!/usr/bin/env node import fs from 'fs'; import os from 'os'; import path from 'path'; import { execFileSync } from 'child_process'; import readline from 'readline'; const AUTHORIZATION = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIyMiJ9.2XLrkX3Fryw4c1Mn3Peqsp8JOMMOu25GP7yxl0gsIG4w9JjfUzlsharGdm8PDusK1fvwMRs5rc1p6LJe0cSg4w'; const BASE_URL = 'https://bops.felo.ai'; const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp', '.avif']); // ── 交互输入 ───────────────────────────────────────────────── function prompt(question) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); }); }); } // ── ffmpeg 转换 ────────────────────────────────────────────── function convertToWebp(inputPath) { const tmpFile = path.join(os.tmpdir(), `case_upload_${Date.now()}_${Math.random().toString(36).slice(2)}.webp`); execFileSync('cwebp', ['-q', '80', '-resize', '800', '0', inputPath, '-o', tmpFile], { stdio: 'pipe' }); return tmpFile; } // ── 上传图片 ───────────────────────────────────────────────── async function uploadFile(filePath) { const formData = new FormData(); const fileData = fs.readFileSync(filePath); const blob = new Blob([fileData], { type: 'image/webp' }); formData.append('file', blob, path.basename(filePath)); const res = await fetch(`${BASE_URL}/api/home/case/upload`, { method: 'POST', headers: { 'Authorization': AUTHORIZATION }, body: formData, }); const body = await res.json(); return { status: res.status, body }; } // ── 创建案例 ───────────────────────────────────────────────── async function createCase(caseTypeId, imageUrl, name, sort) { const res = await fetch(`${BASE_URL}/api/home/case/create`, { method: 'POST', headers: { 'Authorization': AUTHORIZATION, 'Content-Type': 'application/json', }, body: JSON.stringify({ case_type_id: caseTypeId, image_url: imageUrl, name, sort: 0, }), }); const body = await res.json(); return { status: res.status, body }; } // ── 主流程 ─────────────────────────────────────────────────── async function main() { const caseTypeId = await prompt('请输入分类 ID (case_type_id): '); const input = await prompt('请输入图片目录路径: '); const absDir = path.resolve(input); if (!fs.existsSync(absDir)) { console.error(`目录不存在: ${absDir}`); process.exit(1); } const files = fs.readdirSync(absDir) .filter((f) => IMAGE_EXTS.has(path.extname(f).toLowerCase())) .sort(); if (files.length === 0) { console.error('目录中未找到图片文件'); process.exit(1); } console.log(`找到 ${files.length} 张图片,开始处理...\n`); const tmpFiles = []; for (let i = 0; i < files.length; i++) { const file = files[i]; const inputPath = path.join(absDir, file); console.log(`[${i + 1}/${files.length}] ${file}`); // 1. 转换 WebP let tmpPath; try { process.stdout.write(' → ffmpeg 转换... '); tmpPath = convertToWebp(inputPath); tmpFiles.push(tmpPath); console.log('✓'); } catch (err) { console.log(`✗ 转换失败: ${err.message}`); continue; } // 2. 上传 let imageUrl; try { process.stdout.write(' → 上传图片... '); const uploadRes = await uploadFile(tmpPath); console.log(`✓ HTTP ${uploadRes.status}`); console.log(' upload res.data:', JSON.stringify(uploadRes.body, null, 2)); const body = uploadRes.body; imageUrl = body?.data?.url ?? body?.data ?? body?.url ?? null; if (!imageUrl) { console.log(' ⚠ 无法自动提取 image_url,请确认上方打印的字段结构后修改脚本'); continue; } } catch (err) { console.log(`✗ 上传失败: ${err.message}`); continue; } // 3. 创建案例 try { process.stdout.write(` → 创建案例 (sort=${i + 1})... `); const createRes = await createCase(caseTypeId, imageUrl, path.basename(file, path.extname(file)), i + 1); if (createRes.status >= 200 && createRes.status < 300) { console.log(`✓ HTTP ${createRes.status}`); } else { console.log(`✗ HTTP ${createRes.status}: ${JSON.stringify(createRes.body)}`); } } catch (err) { console.log(`✗ 创建失败: ${err.message}`); } console.log(); } for (const f of tmpFiles) { try { fs.unlinkSync(f); } catch {} } console.log('完成。'); } main();