对接文档.md 18 KB

fs-comm-gateway 通讯中间件对接文档

1. 概述

fs-comm-gateway 是 SaaS 平台统一的外呼 / 短信通讯网关,对外提供标准化 HTTP API,对内供工作流引擎、业务服务通过 CommGatewayClient 调用。

主要能力:

能力 说明
AI 外呼 对接 EasyCallCenter365,创建任务、追加名单、启动外呼
短信发送 基于租户短信模板与余额,批量发送 AI 短信
工作流回调 接收 EasyCall / 短信平台回调,续跑阻塞节点
外呼记录查询 callBackUuid 查询外呼日志

默认服务端口:8010
建议通过 Nginx 反向代理暴露:/comm/http://127.0.0.1:8010/comm/


2. 环境与依赖

网关运行依赖以下基础设施(由运维按环境配置):

组件 用途
MySQL 主库 租户信息、公司账号鉴权
MySQL 租户库 业务数据(被叫人、模板、日志等)
Redis Token 会话、限流、工作流回调上下文
EasyCallCenter365 实际外呼平台,easycall.base-url 配置

3. 鉴权方式

网关支持两种调用身份,二选一即可访问业务接口(除白名单路径外)。

3.1 外部对接:JWT Token(推荐三方系统使用)

3.1.1 获取 Token

POST /comm/auth/token
Content-Type: application/json

请求体:

{
  "tenantCode": "your_tenant_code",
  "account": "company_user_account",
  "password": "plain_password"
}
字段 类型 必填 说明
tenantCode string 租户编码
account string 公司端登录账号
password string 登录密码(BCrypt 校验)

成功响应:

{
  "code": 200,
  "msg": "success",
  "data": {
    "accessToken": "eyJhbGciOiJIUzUxMiJ9...",
    "expiresIn": 7200,
    "tokenType": "Bearer",
    "tenantId": 33,
    "companyId": 1001,
    "companyUserId": 2001
  }
}
字段 说明
accessToken JWT,后续请求携带
expiresIn 有效秒数(默认 120 分钟,见 token.expireTime
tokenType 固定 Bearer
tenantId / companyId / companyUserId 当前会话绑定的租户与公司上下文

3.1.2 携带 Token 调用业务接口

Authorization: Bearer {accessToken}
Content-Type: application/json

3.1.3 刷新 / 注销 Token

POST /comm/auth/refresh
Authorization: Bearer {accessToken}
POST /comm/auth/logout
Authorization: Bearer {accessToken}

3.2 内部服务调用:Internal Secret 请求头

平台内部服务(如工作流引擎)通过 CommGatewayClient 调用,无需 JWT,使用共享密钥 + 租户/公司头:

请求头 必填 说明
X-Comm-Internal-Secret 与配置 comm.gateway.internal-secret 一致
X-Comm-Tenant-Id 租户 ID
X-Comm-Company-Id 公司 ID
X-Comm-Company-User-Id 公司用户 ID

配置项(调用方 application.yml):

comm:
  gateway:
    base-url: http://127.0.0.1:8010
    internal-secret: ${COMM_INTERNAL_SECRET}
    enabled: true
    fallback-local: false   # 网关失败时是否降级本地直连

3.3 免鉴权路径

以下路径不需要 Token 或 Internal Secret:

路径 说明
POST /comm/auth/token 登录换 Token
POST /comm/callback/easycall EasyCall 外呼结果回调(含 IP 白名单校验)
POST /comm/callback/sms 短信回执回调
GET /comm/softphone/ws 软电话 ccPhoneBar WebSocket 握手(HTTP 层免 JWT,见 3.4)

3.4 软电话 WebSocket 网关鉴权(方案 A)

ccPhoneBar 控制通道可走 comm-gateway WebSocket 代理(开发环境 ws-proxy-url 配置)。采用双层鉴权

层级 凭证 说明
网关层(前端携带) wsTicket 8006 调用 8010 内部接口申请,Redis 短时有效(默认 60s),握手一次性消费
上游 IPCC(网关内部) loginToken 出票时由 8006 写入票据,前端不感知;8010 验票后透传 IPCC

3.4.1 内部申请 wsTicket

POST /comm/softphone/ws-ticket
Content-Type: application/json
X-Comm-Internal-Secret: {internal-secret}
X-Comm-Tenant-Id: {tenantId}
X-Comm-Company-Id: {companyId}
X-Comm-Company-User-Id: {companyUserId}

请求体:

{
  "loginToken": "easycall_login_token",
  "extNum": "1001"
}

成功响应:

{
  "code": 200,
  "msg": "success",
  "data": {
    "wsTicket": "uuid",
    "expiresIn": 60
  }
}

业务服务在 getToolbarBasicParam 合并配置时自动调用(CommGatewayClient.issueSoftPhoneWsTicket),前端从接口响应读取 wsTicket不返回 loginToken)。

3.4.2 前端 WebSocket 连接

ws://{gateway}/comm/softphone/ws?wsTicket={wsTicket}
  • 缺少 wsTicket 或票据过期/已使用时,握手被拒绝。
  • loginToken 仅存在于服务端票据中,由网关连接上游 IPCC 时自动附带。
  • JsSIP 通话 WSS(8443)仍直连 IPCC,不走本代理。
  • 若浏览器报 Unexpected response code: 200(响应头含 Vary: Origin),根因是 fs-frameworkResourcesConfig/** 注册了全局 CorsFilter,阻断了 WebSocket Upgrade。comm-gateway 启动类已排除 ResourcesConfig,并由 CommGatewayCorsFilter/comm/softphone/ws 强制绕过 CORS。修改后须重启 8010

3.4.3 配置项

comm:
  gateway:
    softphone:
      ws-ticket-ttl-seconds: 60   # 8010 票据 TTL

ai-sip-call:
  softphone:
    ws-proxy-url: ws://127.0.0.1:8010/comm/softphone/ws   # 8006 下发给前端;为空则直连 IPCC

4. 统一响应格式

业务 Controller 统一返回 CommApiResult

{
  "code": 200,
  "msg": "success",
  "data": { }
}
code 含义
200 成功
401 未认证(如查询接口未带 Token)
404 资源不存在
500 业务失败或系统异常

判定规则:

  • HTTP 状态码通常为 200,请以响应体 code 字段 判断业务成败。
  • code != 200 时,msg 为失败原因,对接方应记录并向上游返回失败。

常见失败 msg 示例:

msg 场景
被叫人手机号解密失败或号码无效 被叫号码为空或解密失败
被叫人命中外呼黑名单 黑名单拦截
外呼名单追加失败或线路限流 EasyCall 未追加名单或线路限流
成功追加0个名单 EasyCall 返回 0 条(号码无效等)
无权使用该外呼线路 gatewayId 不在公司可用线路内
租户请求频率超限,请稍后重试 触发 QPS 限流
剩余短信数量不足,请充值 短信余额不足
短信模板不存在或未审核 模板无效

注意:Filter 层未授权时返回 AjaxResult 格式(code: 401),与 CommApiResult 字段结构一致。


5. 接口明细

5.1 发起外呼

POST /comm/call/send
Authorization: Bearer {accessToken}
Content-Type: application/json

请求体:

{
  "calleeId": 25,
  "roboticId": 174,
  "gatewayId": 5,
  "businessId": null,
  "nodeKey": "call_node_1",
  "workflowInstanceId": "wf-instance-uuid",
  "callbackUrl": "",
  "phone": null,
  "llmAccountId": 1,
  "voiceCode": "xiaoyun",
  "voiceSource": "ali",
  "busiGroupId": 10,
  "maxConcurrency": 1,
  "bizParams": {}
}
字段 类型 必填 说明
calleeId long 被叫人 ID(company_voice_robotic_callees.id
roboticId long AI 外呼任务 ID
gatewayId long EasyCall 外呼线路 ID
businessId long 商机 ID,传入时校验当日拨打次数上限
nodeKey string 工作流场景必填 工作流节点 Key
workflowInstanceId string 工作流场景必填 工作流实例 ID
callbackUrl string 自定义 EasyCall 回调地址,空则读租户/公司配置
phone string 指定被叫号码(明文或 AES 密文);为空则从 calleeId 解析
llmAccountId / voiceCode / voiceSource / busiGroupId / maxConcurrency - AI 外呼扩展参数(工作流节点配置透传)
bizParams object 追加到 EasyCall bizJson 的自定义字段

成功响应 data:

{
  "callBackUuid": "4ca54a59-fcdf-4c55-8783-1112dd3405cf",
  "batchId": 159576,
  "phone": "13800138000"
}
字段 说明
callBackUuid 本次外呼唯一标识,用于查询与回调关联
batchId EasyCall 任务批次 ID
phone 实际外呼号码(明文)

处理流程简述:

  1. 校验线路归属、黑名单、号码有效性
  2. 写入 Redis 工作流回调上下文(easycall:workflow:callback:{callBackUuid}
  3. 调用 EasyCall addCallList + startTask
  4. 异步写入租户库 company_voice_robotic_call_log_callphone

5.2 发送短信

POST /comm/sms/send
Authorization: Bearer {accessToken}
Content-Type: application/json

请求体:

{
  "roboticId": 174,
  "calleeId": 25,
  "smsTempId": 88,
  "nodeKey": "sms_node_1",
  "workflowInstanceId": "wf-instance-uuid",
  "phone": null,
  "customerId": null,
  "companyUserId": null,
  "senderName": null,
  "cardUrl": null,
  "templateParams": {}
}
字段 类型 必填 说明
roboticId long AI 任务 ID
calleeId long 被叫人 ID
smsTempId long 短信模板 ID(须已审核且启用)
nodeKey string 工作流场景建议填 节点 Key
workflowInstanceId string 工作流场景建议填 工作流实例 ID
phone string 指定手机号,默认取客户 mobile
customerId long 客户 ID,默认取 callee 关联 userId
companyUserId / senderName - 发送销售信息,空则自动从微信绑定关系解析
cardUrl string 卡片链接
templateParams map 模板变量(预留)

成功响应 data:

{
  "callbackUuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "customerId": 10001,
  "phone": "13800138000"
}

发送结果异步写入租户库 company_voice_robotic_call_log_sendmsg(status:1 进行中 / 2 成功 / 3 失败)。

迈远(provider=my)发送说明:

网关进程内由 MyCommSmsProvider 负责实际 HTTP 下发,不再读取旧的 his.sms 全局配置,而是按租户在 Admin 配置的接口表路由:

配置表 字段 说明
company_sms_api provider=my 固定为迈远
url 接口根地址
account / password 账户密码
sign 短信签名
company_sms_api_port port_no 迈远扩展码 extno
account / password / sign 可选,覆盖接口级配置

请求 URL 格式(与旧版 sendCaptcha 一致):

{url}sms?action=send&account={account}&password={password}&mobile={phone}&content={URLEncode(sign+content)}&extno={extno}&rt=json
  • 模板类型 tempType=1(行业通知):内容为 sign + content
  • 模板类型 tempType=2(营销):内容为 sign + content + 拒收请回复R

5.3 查询外呼记录

GET /comm/query/call/{callBackUuid}
Authorization: Bearer {accessToken}

成功响应 data: 外呼日志对象(company_voice_robotic_call_log_callphone 表字段 JSON 化),含 statusresultcallTimeintention 等。

失败示例:

{
  "code": 404,
  "msg": "未找到外呼记录",
  "data": null
}

5.4 平台回调接口(运维 / EasyCall 配置)

EasyCall 外呼回调

POST /comm/callback/easycall
Content-Type: application/json
  • 请求体:EasyCall CDR JSON 字符串(原样 POST)
  • 响应:成功返回 "success",非法 IP 返回 "illegal IP"
  • 安全:@CallbackIpCheck 校验来源 IP 是否在租户 cId.config.legalIPs 白名单内
  • 处理:解析 bizJson.tenantId 切换租户库,更新外呼日志并续跑工作流阻塞节点

EasyCall 侧需配置的回调地址示例:

https://{your-domain}/comm/callback/easycall

bizJson 中需包含(网关外呼时自动写入):

{
  "tenantId": 33,
  "callBackUuid": "4ca54a59-fcdf-4c55-8783-1112dd3405cf",
  "callBackUrl": "",
  "custName": "张三"
}

短信回执回调

POST /comm/callback/sms
Content-Type: application/json
  • 请求体:短信平台回执 JSON,需包含 tenantId 字段以便切库
  • 响应:由 ISmsService.smsNotify 返回(通常为 "success" 或平台约定字符串)

6. 对接时序

6.1 外呼 + 工作流续跑

sequenceDiagram
    participant Client as 调用方
    participant GW as fs-comm-gateway
    participant EC as EasyCall
    participant WF as 工作流引擎

    Client->>GW: POST /comm/call/send
    GW->>GW: 校验鉴权/线路/号码/黑名单
    GW->>EC: addCallList + startTask
    GW-->>Client: callBackUuid, batchId, phone

    EC->>GW: POST /comm/callback/easycall
    GW->>GW: 切租户库、更新 call_log
    GW->>WF: resumeFromBlockingNode

6.2 三方系统最小对接步骤

  1. 调用 /comm/auth/token 获取 accessToken
  2. 准备业务数据:roboticIdcalleeIdgatewayId(或 smsTempId
  3. 调用 /comm/call/send/comm/sms/send
  4. 必须检查响应 code === 200,并保存 callBackUuid / callbackUuid
  5. 轮询 GET /comm/query/call/{callBackUuid} 或等待 EasyCall 回调触发后续流程

7. 限流与线路鉴权

7.1 租户 QPS 限流

  • 配置项:comm.gateway.tenant-qps-limit(默认 200)
  • 外呼、短信发送前均校验
  • 超限返回:租户请求频率超限,请稍后重试

7.2 外呼线路鉴权

gatewayId 必须属于当前公司可用线路,校验顺序:

  1. EasyCall getGatewayList(companyId) 返回列表
  2. 否则读公司 gateWayList 配置
  3. 否则读全局 cId.config.showGatewayIds

8. 配置参考

application.yml 核心项(生产环境请通过环境变量或配置中心注入,勿提交明文密钥):

server:
  port: 8010

token:
  header: Authorization
  secret: ${COMM_TOKEN_SECRET}
  expireTime: 120          # Token 有效分钟数

comm:
  gateway:
    internal-secret: ${COMM_INTERNAL_SECRET}
    tenant-qps-limit: 200
    executor:
      core-pool-size: 20
      max-pool-size: 100
      queue-capacity: 2000

easycall:
  base-url: http://{easycall-host}:{port}

9. 调用示例

9.1 cURL:登录 + 外呼

# 1. 获取 Token
TOKEN_RESP=$(curl -s -X POST "http://127.0.0.1:8010/comm/auth/token" \
  -H "Content-Type: application/json" \
  -d '{"tenantCode":"demo","account":"admin","password":"your_password"}')

ACCESS_TOKEN=$(echo $TOKEN_RESP | jq -r '.data.accessToken')

# 2. 发起外呼
curl -s -X POST "http://127.0.0.1:8010/comm/call/send" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "calleeId": 25,
    "roboticId": 174,
    "gatewayId": 5,
    "nodeKey": "node_call_1",
    "workflowInstanceId": "your-workflow-instance-id"
  }'

9.2 cURL:内部服务调用

curl -s -X POST "http://127.0.0.1:8010/comm/call/send" \
  -H "Content-Type: application/json" \
  -H "X-Comm-Internal-Secret: your_internal_secret" \
  -H "X-Comm-Tenant-Id: 33" \
  -H "X-Comm-Company-Id: 1001" \
  -d '{
    "calleeId": 25,
    "roboticId": 174,
    "gatewayId": 5,
    "nodeKey": "node_call_1",
    "workflowInstanceId": "your-workflow-instance-id"
  }'

9.3 Java(内部 CommGatewayClient)

Map<String, Object> body = new HashMap<>();
body.put("calleeId", 25L);
body.put("roboticId", 174L);
body.put("gatewayId", 5L);
body.put("nodeKey", "node_call_1");
body.put("workflowInstanceId", workflowInstanceId);

JSONObject result = commGatewayClient.sendCall(tenantId, companyId, companyUserId, body);
String callBackUuid = result.getString("callBackUuid");
String phone = result.getString("phone");

10. 部署说明

10.1 构建

cd ylrz_saas_his_scrm
mvn clean package -pl fs-comm-gateway -am -DskipTests

10.2 Docker

docker build -t fs-comm-gateway:latest fs-comm-gateway/
docker run -d -p 8010:8010 \
  -e SPRING_PROFILES_ACTIVE=prod \
  fs-comm-gateway:latest

10.3 Nginx 反代

参考模块内 nginx.conf

location /comm/ {
    proxy_pass http://127.0.0.1:8010/comm/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 120s;
}

# 软电话 WebSocket 代理需额外升级头
location /comm/softphone/ws {
    proxy_pass http://127.0.0.1:8010/comm/softphone/ws;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 600s;
}

11. 附录:相关数据表

表名 说明
comm_gateway_api_log 主库 网关 API 调用日志(频率计数、Admin 查询)
company_comm_gateway_log 租户库 网关 API 调用记录(成功/失败/限频均写入)
company_voice_robotic_call_log_callphone 租户库 AI 外呼执行日志
company_voice_robotic_call_log_sendmsg 租户库 短信发送日志
company_voice_robotic_call_log_addwx 租户库 加微执行日志(其他模块写入)

外呼/短信业务日志与 company_comm_gateway_log 均写入当前租户库comm_gateway_api_log 写入主库用于全平台统计与频率限制计数。

存量租户需执行:fs-service/src/main/resources/db/20250604-company-comm-gateway-log.sql


12. 版本与联系

模块 fs-comm-gateway
默认端口 8010
API 前缀 /comm
文档更新 2026-06-03

如有对接问题,请提供:tenantIdcallBackUuid、请求体、完整响应 JSON 及服务端日志时间点,便于排查。