-.zip, * 再本脚本 POST 到 /appmall/api/addon/submit(与开发者控制台「提交应用」同一条 submit 链路)。 * * 用法: * php scripts/submit_template.php [api_url] [--title=...] [--price=...] [--category=...] * [--author=...] [--tags=...] [--logo=...] [--description=...] [--changelog=...] * [--require_framework=...] [-k] * - api_url 缺省 https://www.ywxapp.cn(生产);本地可用 http://localhost:端口 * - -k 不校验 SSL(本地自签证书调试用) * - 元数据优先取命令行覆盖项,否则从 zip 内 template.json 读取 * * 例: * php scripts/submit_template.php runtime/templates/myblog-1.0.0.zip YOUR_TOKEN * php scripts/submit_template.php runtime/templates/myblog-1.0.0.zip YOUR_TOKEN http://localhost:8080 -k * * 提交接口返回:code=1 成功(读 msg),code=0 失败(读 msg/message)。 */ declare(strict_types=1); // ===================== 解析参数 ===================== $insecure = false; $opts = [ 'title' => null, 'price' => null, 'category' => null, 'author' => null, 'tags' => null, 'logo' => null, 'description' => null, 'changelog' => null, 'require_framework' => null, ]; $pos = []; foreach (array_slice($argv, 1) as $a) { if ($a === '-k') { $insecure = true; continue; } if (strpos($a, '--') === 0 && strpos($a, '=') !== false) { [$k, $v] = explode('=', $a, 2); $k = substr($k, 2); if (array_key_exists($k, $opts)) { $opts[$k] = $v; } else { fwrite(STDERR, "未知选项: --{$k}\n"); exit(1); } continue; } $pos[] = $a; } $zip = $pos[0] ?? ''; $token = $pos[1] ?? ''; $apiUrl = $pos[2] ?? 'https://www.ywxapp.cn'; if ($zip === '' || $token === '') { fwrite(STDERR, "用法: php scripts/submit_template.php [api_url] [--title=...] ... [-k]\n"); exit(1); } $zip = realpath($zip); if (!is_file($zip)) { fwrite(STDERR, "zip 不存在: {$pos[0]}\n"); exit(1); } // ===================== 读取 zip 内 template.json ===================== if (!class_exists('ZipArchive')) { fwrite(STDERR, "ZipArchive 扩展不可用\n"); exit(1); } $z = new ZipArchive(); if ($z->open($zip) !== true) { fwrite(STDERR, "无法打开 zip: {$zip}\n"); exit(1); } $jsonName = null; for ($i = 0; $i < $z->numFiles; $i++) { $n = $z->statIndex($i)['name']; if ($n === 'template.json' || preg_match('#^templates/[^/]+/template\.json$#', $n)) { $jsonName = $n; break; } } if ($jsonName === null) { fwrite(STDERR, "zip 内缺少 template.json(须为 templates/<名称>/template.json 结构)\n"); exit(1); } $meta = json_decode((string) $z->getFromName($jsonName), true); $z->close(); if (!is_array($meta)) { fwrite(STDERR, "template.json 解析失败\n"); exit(1); } // 模板标识:根目录 template.json 取 meta.name;templates//template.json 取目录名 if (preg_match('#^template\.json$#', $jsonName)) { $name = $meta['name'] ?? ''; } else { $segs = explode('/', $jsonName); $name = $segs[1] ?? ($meta['name'] ?? ''); } // ===================== 元数据合并(命令行覆盖优先) ===================== $version = $meta['version'] ?? ''; $title = $opts['title'] ?? ($meta['title'] ?? $name); $author = $opts['author'] ?? ($meta['author'] ?? ''); $category = $opts['category'] ?? ($meta['category'] ?? ''); $tags = $opts['tags'] ?? ($meta['tags'] ?? ''); $price = (string) ($opts['price'] ?? ($meta['price'] ?? 0)); $logo = $opts['logo'] ?? ($meta['logo'] ?? ''); $description = $opts['description'] ?? ($meta['description'] ?? ''); $changelog = $opts['changelog'] ?? ($meta['changelog'] ?? ''); $requireFramework = $opts['require_framework'] ?? ($meta['require_framework'] ?? ''); // 基础校验(与 api/Market::submit 一致) if (!preg_match('/^[a-zA-Z0-9_]+$/', $name)) { fwrite(STDERR, "标识格式不正确(仅字母数字下划线): {$name}\n"); exit(1); } if (!preg_match('/^\d+\.\d+\.\d+$/', $version)) { fwrite(STDERR, "版本号格式不正确(需 x.y.z): {$version}\n"); exit(1); } echo "[提交] name={$name} version={$version} title={$title}\n"; echo "[提交] 目标: {$apiUrl}/appmall/api/addon/submit (type=template)\n"; // ===================== 发起 multipart 提交 ===================== if (!function_exists('curl_init')) { fwrite(STDERR, "curl 扩展不可用\n"); exit(1); } $post = [ 'token' => $token, 'type' => 'template', 'name' => $name, 'version' => $version, 'title' => $title, 'author' => $author, 'category' => $category, 'tags' => $tags, 'price' => $price, 'logo' => $logo, 'description' => $description, 'changelog' => $changelog, 'require_framework' => $requireFramework, 'file' => new CURLFile($zip, 'application/zip', basename($zip)), ]; $ch = curl_init($apiUrl . '/appmall/api/addon/submit'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => $post, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 3600, CURLOPT_CONNECTTIMEOUT => 30, CURLOPT_SSL_VERIFYPEER => !$insecure, CURLOPT_SSL_VERIFYHOST => $insecure ? 0 : 2, ]); $resp = curl_exec($ch); if ($resp === false) { fwrite(STDERR, "curl 错误: " . curl_error($ch) . "\n"); curl_close($ch); exit(1); } $httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode((string) $resp, true); if (!is_array($data)) { fwrite(STDERR, "非 JSON 响应 (HTTP {$httpCode}):\n{$resp}\n"); exit(1); } // submit 成功 code=1 if (($data['code'] ?? 0) === 1) { echo "提交成功: " . ($data['msg'] ?? '') . "\n"; exit(0); } echo "提交失败: " . ($data['msg'] ?? ($data['message'] ?? "HTTP {$httpCode}")) . "\n"; exit(1);