__DIR__ . '/../app/adminapi/logic/firstvisit/WecomPromotionLogic.php', 'controller' => __DIR__ . '/../app/adminapi/controller/firstvisit/WecomPromotionController.php', 'middleware' => __DIR__ . '/../app/adminapi/http/middleware/AuthMiddleware.php', 'api' => $root . '/admin/src/api/first_visit.ts', 'page' => $root . '/admin/src/views/first_visit/wecom_promotion/index.vue', 'automationForm' => $root . '/admin/src/views/first_visit/wecom_promotion/components/PromotionAutomationForm.vue', ]; $sources = []; foreach ($paths as $name => $path) { $source = file_get_contents($path); if (!is_string($source)) { throw new RuntimeException("无法读取 {$name}: {$path}"); } $sources[$name] = $source; } if (!str_contains($sources['controller'], 'public function batchUpdatePools()') || !str_contains($sources['controller'], 'WecomPromotionLogic::batchUpdatePools(')) { throw new RuntimeException('控制器缺少批量修改方案配置接口'); } if (!str_contains($sources['middleware'], "'firstvisit.wecompromotion/batchupdatepools'")) { throw new RuntimeException('批量修改方案接口未加入获客助手权限白名单'); } if (!str_contains($sources['api'], '/firstvisit.wecomPromotion/batchUpdatePools') || !str_contains($sources['api'], 'WecomPromotionBatchUpdatePoolsParams')) { throw new RuntimeException('前端缺少类型化批量修改 API'); } $logic = $sources['logic']; $start = strpos($logic, 'public static function batchUpdatePools('); $end = strpos($logic, 'public static function saveWidget(', $start === false ? 0 : $start); if ($start === false || $end === false) { throw new RuntimeException('无法定位 batchUpdatePools 方法'); } $method = substr($logic, $start, $end - $start); foreach ([ 'assertBasePagePermission($adminId, $adminInfo)', "array_key_exists('fallback_url', \$changes)", "array_key_exists('status', \$changes)", "array_key_exists('skip_verify', \$changes)", "array_key_exists('automation_config', \$changes)", 'array_replace($currentAutomation, $automationPatch)', 'self::savePool($saveParams, $adminId, $adminInfo, false)', "'sync_error_count' => \$syncErrorCount", "'sync_queued_count' => \$syncQueuedCount", "'results' => \$results", ] as $needle) { if (!str_contains($method, $needle)) { throw new RuntimeException("批量修改方案逻辑缺少契约:{$needle}"); } } $permissionValidation = strpos($method, "self::assertScopedRow("); $writeLoop = strpos($method, '$results = []'); if ($permissionValidation === false || $writeLoop === false || $permissionValidation > $writeLoop) { throw new RuntimeException('批量修改必须在任何方案保存前完成全部方案权限校验'); } if (!str_contains($method, "false\n );")) { throw new RuntimeException('批量修改不得允许共享操作人绕过原管理范围批量编辑'); } $page = $sources['page']; foreach ([ '批量修改方案', '仅勾选的项目会覆盖到所选方案', 'batchConfigApply.skip_verify', 'batchConfigApply.fallback_url', 'batchConfigApply.status', 'batchConfigApply.reception', 'batchConfigApply.customer', 'batchConfigApply.welcome', 'batchSharedPrimaryMemberIds', 'batchPrimaryMemberUnion', 'wecomPromotionBatchUpdatePools', ] as $needle) { if (!str_contains($page, $needle)) { throw new RuntimeException("前端批量修改交互缺少 {$needle}"); } } if (!str_contains($sources['automationForm'], 'disabledSections?: AutomationSection[]') || !str_contains($sources['automationForm'], 'backupExcludedMemberIds?: number[]')) { throw new RuntimeException('自动化表单未支持批量场景的分组禁用或主成员冲突约束'); } echo "WECOM_PROMOTION_BATCH_UPDATE_CONTRACT_OK\n";