## GitHub 同步说明 | 项 | 内容 | |:---|:---| | 仓库 | https://github.com/fnvtk/cunkebao_v3 | | 分支 | develop | | 方向 | 本地 → GitHub(单向 push) | | 是否拉取远程 | 否(未 fetch / pull / merge) | | 是否改本地源文件 | 否(仅 git add/commit,未编辑业务/文档正文) | | 远程基准 | origin/develop @b5b40e8b| | 本批工作区变更 | 999 files, +47622 / -4007 lines | ## 一并推送的本地已有 commit(此前未 push) 1.3aa7ecf8deploy: 官网 ckb.quwanzhi.com 宝塔上线 DNS+SSL+验收文档 2.16a70045fix(官网): 桌面导航仅保留一个留资 CTA 并重新上线 3.dbe7b7aafix(官网): Hero 改为主 CTA 按钮并去掉重复 1200+ 数据条 4.ac82726echore(部署): 五端上线验收46项并统一留资与缓存版本 ## 本 commit 目录统计 | 目录 | 文件数 | 说明 | |:---|---:|:---| | 开发文档/ | 639 | 10、项目管理、未完成项跨仓汇总、五端需求/部署/接口 | | Server/ | 129 | 后端 API、迁移、部署相关 | | Touchkebao/ | 80 | PC 工作台、获客、算力 | | 官网/ | 61 | ckb.quwanzhi.com 静态站与 CTA 迭代 | | Cunkebao/ | 49 | 移动端场景/设置/流量池 | | SuperAdmin/ | 26 | 超管后台 | | .cursor/ | 11 | 规则与 Skill | | 其他 | 14 | docker-compose、.obsidian、.codegraph 等 | ## 重点文件(本地为准) - 开发文档/1、需求/修改/未完成项与跨仓验收汇总.md - 开发文档/10、项目管理/账号密码与登录环境一览.md - 开发文档/10、项目管理/(新建项目管理目录与截图) - 官网/ 全站 + 宝塔上线文档 - 五端上线验收 46 项(见 ac82726e) ## 刻意未上传 - node_modules/、.DS_Store、.smart-env/ - .开发文档_nested_git_backup/ - **/__pycache__/ ## 验收 - 提交页(含本说明): https://github.com/fnvtk/cunkebao_v3/commit/HEAD - 分支树: https://github.com/fnvtk/cunkebao_v3/tree/develop Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
4.1 KiB
PHP
115 lines
4.1 KiB
PHP
#!/usr/bin/env php
|
||
<?php
|
||
/**
|
||
* 场景获客 Webhook 推送 — 本地 smoke test(不依赖数据库)
|
||
* 用法:php Server/tests/plan_lead_webhook_smoke.php
|
||
*/
|
||
|
||
define('APP_PATH', dirname(__DIR__) . '/application/');
|
||
require dirname(__DIR__) . '/thinkphp/base.php';
|
||
|
||
// 仅加载服务类所需的最小环境
|
||
spl_autoload_register(function ($class) {
|
||
$map = [
|
||
'app\\cunkebao\\service\\PlanLeadWebhookService' => dirname(__DIR__) . '/application/cunkebao/service/PlanLeadWebhookService.php',
|
||
'app\\cunkebao\\service\\WebhookUrlValidator' => dirname(__DIR__) . '/application/cunkebao/service/WebhookUrlValidator.php',
|
||
];
|
||
if (isset($map[$class]) && is_file($map[$class])) {
|
||
require $map[$class];
|
||
}
|
||
});
|
||
|
||
use app\cunkebao\service\PlanLeadWebhookService;
|
||
use app\cunkebao\service\WebhookUrlValidator;
|
||
|
||
$passed = 0;
|
||
$failed = 0;
|
||
|
||
function assert_true(bool $cond, string $label): void
|
||
{
|
||
global $passed, $failed;
|
||
if ($cond) {
|
||
echo "[PASS] {$label}\n";
|
||
$passed++;
|
||
} else {
|
||
echo "[FAIL] {$label}\n";
|
||
$failed++;
|
||
}
|
||
}
|
||
|
||
// 1. validateConfig
|
||
assert_true(
|
||
PlanLeadWebhookService::validateConfig(['leadPushWebhookEnabled' => false]) === null,
|
||
'未启用时不校验'
|
||
);
|
||
assert_true(
|
||
PlanLeadWebhookService::validateConfig(['leadPushWebhookEnabled' => true, 'leadPushWebhook' => '']) !== null,
|
||
'启用但无 URL 应报错'
|
||
);
|
||
assert_true(
|
||
PlanLeadWebhookService::validateConfig(['leadPushWebhookEnabled' => true, 'leadPushWebhook' => 'ftp://x']) !== null,
|
||
'非 http(s) URL 应报错'
|
||
);
|
||
assert_true(
|
||
PlanLeadWebhookService::validateConfig(['leadPushWebhookEnabled' => true, 'leadPushWebhook' => 'https://example.com/hook']) === null,
|
||
'合法 https URL 通过'
|
||
);
|
||
assert_true(
|
||
PlanLeadWebhookService::validateConfig(['leadPushWebhookEnabled' => true, 'leadPushWebhook' => 'http://169.254.169.254/latest/meta-data/']) !== null,
|
||
'metadata IP 应拒绝'
|
||
);
|
||
assert_true(
|
||
WebhookUrlValidator::validate('http://127.0.0.1/hook') !== null,
|
||
'127.0.0.1 应拒绝'
|
||
);
|
||
|
||
// 2. parseWebhookConfig
|
||
$config = PlanLeadWebhookService::parseWebhookConfig([
|
||
'sceneConf' => json_encode([
|
||
'leadPushWebhookEnabled' => true,
|
||
'leadPushWebhook' => 'https://hook.test/ckb',
|
||
'leadPushWebhookSecret' => 'secret123',
|
||
]),
|
||
]);
|
||
assert_true($config['enabled'] === true, 'parse enabled');
|
||
assert_true($config['url'] === 'https://hook.test/ckb', 'parse url');
|
||
assert_true($config['secret'] === 'secret123', 'parse secret');
|
||
|
||
// 3. buildTestPayload(不依赖 DB)
|
||
$testPayload = PlanLeadWebhookService::buildTestPayload(['id' => 5, 'name' => '全局', 'sceneId' => 1, 'planType' => 0]);
|
||
assert_true($testPayload['event'] === 'lead.test', 'test event');
|
||
assert_true($testPayload['lead']['phone'] === '13800138000', 'test lead.phone');
|
||
assert_true(isset($testPayload['behaviors'][0]['behaviorName']), 'test behaviors');
|
||
|
||
// 4. buildPayload — 需完整框架与 DB(含 Model + database.datetime_format 配置)。
|
||
// 本 smoke 默认「不依赖数据库」,故无 DB/配置时优雅 SKIP,不计失败。
|
||
try {
|
||
$payload = PlanLeadWebhookService::buildPayload(
|
||
['id' => 1, 'name' => '测试计划', 'sceneId' => 1, 'planType' => 0, 'companyId' => 1],
|
||
[
|
||
'id' => 99,
|
||
'phone' => '13800138000',
|
||
'name' => '张三',
|
||
'source' => '测试',
|
||
'remark' => '',
|
||
'tags' => json_encode(['高意向']),
|
||
'siteTags' => json_encode(['新客']),
|
||
'status' => 0,
|
||
'channelId' => 0,
|
||
'fail_reason' => '',
|
||
'createTime' => time(),
|
||
'addTime' => 0,
|
||
'passTime' => 0,
|
||
'updateTime' => time(),
|
||
],
|
||
PlanLeadWebhookService::EVENT_CREATED
|
||
);
|
||
assert_true($payload['event'] === 'lead.created', 'event 字段');
|
||
assert_true(in_array('高意向', $payload['lead']['tags'], true), 'lead.tags 解码');
|
||
} catch (\Throwable $e) {
|
||
echo "[SKIP] buildPayload(需 ThinkPHP 完整引导 + DB):" . $e->getMessage() . "\n";
|
||
}
|
||
|
||
echo "\n--- 结果: {$passed} 通过, {$failed} 失败 ---\n";
|
||
exit($failed > 0 ? 1 : 0);
|