# SaaS Server:最小外呼测试工具 使用 Fiber/v3,代替浏览器操作:提供 Dispatcher 所需配置,手动向测试队列发送一条外呼,查看回执和最终结果。 **不修改 Dispatcher/Agent,不绕过它们的业务检查。启动不自动发消息。仅支持专用 `saas-mock-*` vhost,不能和真实 SaaS 共用反馈队列。** 本工具不授权真实拨号或真实 AI/OSS 调用。 ## 启动 ```sh go build -o /tmp/saas-server ./cmd/saas-server /tmp/saas-server --config /absolute/path/server.json ``` `server.json` 示例(全部为占位,须换成已批准的测试配置): ```json { "listen": "0.0.0.0:8089", "dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6", "dispatcher_secret": "TEST_DISPATCHER_SECRET", "broker_url": "amqp://TEST_USER:TEST_PASSWORD@127.0.0.1:5672/saas-mock-example", "snapshots": "snapshots", "feedback_dir": "feedback", "tls_cert": "dispatcher-test.crt", "tls_key": "dispatcher-test.key" } ``` - 相对路径以 `server.json` 所在目录为基准。 - `listen` 省略时为 `0.0.0.0:8089`。 - TLS 两项可同时省略,服务改为 HTTP;需要遵循现有 Dispatcher 客户端的连接要求,不能为测试放松客户端校验。 - 启动文件和各快照必须是私有普通文件(如 `0600`)。反馈目录独立且私有(如 `0700`),不能与输入配置目录交叠。 - MQ 用户必须能够在这个专用 vhost 声明、绑定和使用现行拓扑。已有拓扑不匹配或已有独占反馈消费者时明确失败;不会清队列或自动恢复连接。 快照目录: ```text snapshots/ sip.json providers.json quota.json tasks/ .json ``` JSON 内容直接采用 [`../../contracts/schema/`](../../contracts/schema/README.md) 的现行配置响应格式,不使用旧 ENV 替代。所有资源必须属于启动文件中的 Dispatcher;首期仅一个租户,任务文件名与 task_id 一致。任务列表由详情生成,不另建列表文件。 AI 连接、模型、音色、参数、SIP 线路、额度和时段来自这些文件;合同示例只是结构参考,不保证线路具备执行参数,也不是拨号授权。 另行启动使用这套测试配置及专用 MQ 的 Dispatcher/Agent;本工具不启动、停止、重启或修改现有服务。五类正式配置接口校验现有 `X-DISPATCHER-id` 和 `X-DISPATCHER-SECRET-KEY` 请求头。 ## 手动发送一条外呼 以下命令以本地 HTTP 为例;启用 TLS 时改用 HTTPS 并使用测试 CA 的 `--cacert`,不要关闭证书校验。 ```sh BASE=http://127.0.0.1:8089 # 任务、号码必须是本次获准的测试数据;123456 只是占位。 curl --fail-with-body "$BASE/test/calls" \ -H 'Content-Type: application/json' \ -d '{"event_id":"test-numberID-001","task_id":"1","callee":"123456"}' ``` `event_id` 由调用者明确指定,原样保留。相同身份、相同请求在本次进程中不再发送;同身份不同内容报错。重启会清除发送记录,**不要通过重启、换 ID 或重复调用补拨**;Dispatcher 的既有重复消息规则不变。 响应中的 `status`: | 值 | 含义 | | --- | --- | | `confirmed` | 队列已确认,不表示已拨号或已接通 | | `unroutable` | 消息被队列退回,没有对应路由 | | `rejected` | 队列明确拒绝 | | `not_sent` | 发送前失败 | | `unknown` | 是否发送成功不确定;禁止自动重发 | 非 `confirmed` 返回 HTTP 503,并保留本次状态。输入错误返回 HTTP 400。工具没有自动重试、批量、定时或换线功能。 ## 查询和校验反馈 ```sh curl --fail-with-body --get "$BASE/test/events" \ --data-urlencode 'event_id=test-numberID-001' ``` 返回本次进程内的 `requests` 与 `feedback`。派发回执和最终结果分别记录;`validation` 为 `valid` 或 `invalid`,错误原因会明确显示。等待不到反馈不等于通话失败,只有拒绝的未发起请求不一定有通话最终结果。 查询只返回身份、状态、结果及校验摘要,不显示完整对话/音频等正文。收到的原消息保存在独立反馈目录的随机 `feedback-*.json` 文件中,文件保存并同步成功后才 ACK。保存失败停止消费并退出,不丢弃消息。重复或错误消息也保留证据,冲突不覆盖原记录。 **重启后不恢复历史查询;旧反馈文件仍保留,供人工检查。本工具不会回写配置,也不会保存内存修改。** ## 内存修改配置 使用完整配置响应,不使用 PATCH 或字段合并: ```sh curl --fail-with-body -X PUT "$BASE/test/config/sip" \ -H 'Content-Type: application/json' --data-binary @/path/candidate-sip.json curl --fail-with-body -X PUT "$BASE/test/config/providers" \ -H 'Content-Type: application/json' --data-binary @/path/candidate-providers.json curl --fail-with-body -X PUT "$BASE/test/config/quota" \ -H 'Content-Type: application/json' --data-binary @/path/candidate-quota.json curl --fail-with-body -X PUT "$BASE/test/config/tasks/1" \ -H 'Content-Type: application/json' --data-binary @/path/candidate-task.json ``` 校验成功后整体替换内存,失败不改变当前值。只能修改已加载资源,不能增删任务或更换租户/Dispatcher 身份。重启重新加载原文件。 修改配置不会自动通知 Dispatcher,需要显式操作: ```sh # action 只支持 start、resume、edit;每条指令使用明确的源 event_id。 curl --fail-with-body "$BASE/test/task-controls" \ -H 'Content-Type: application/json' \ -d '{"event_id":"test-edit-001","task_id":"1","action":"edit","reason":"test configuration change"}' # change_type 按现行合同:enabled、disabled、removed、created;revision 为正整数。 curl --fail-with-body "$BASE/test/sip-refresh" \ -H 'Content-Type: application/json' \ -d '{"event_id":"test-sip-001","trunk_id":"trunk-mock","change_type":"enabled","revision":8}' ``` 任务/额度按现行 start/resume/edit 流程读取,edit 不解除暂停、不恢复已停止任务。SIP 通知触发现行加载流程;已经发出的通话不改变快照。AI providers 只在 Dispatcher 启动读取,修改后须另行安排重启;本工具不重启服务。 ## 本地验证 ```sh go test -race ./cmd/saas-server # 需 Docker 和已存在的 rabbitmq:4.1-management-alpine 镜像,不自动拉取。 bash cmd/saas-server/check-integration.sh ``` 集成脚本只创建并清理自己的临时 RabbitMQ 容器,运行 Fiber HTTPS → RabbitMQ → 未修改的 Dispatcher → Mock Agent mTLS → 回执/最终结果测试。使用临时配置、数据库和合成音频,不拨号,不访问真实 AI/OSS。通过不代表真实服务或通话验收。