Files
cunkebao_v3/Server/application/common/controller/OpenScenariosController.php
Manus AI 5517457929 sync: 以本地为准同步全量变更至 GitHub
含四端需求文档、前端/后端/超管/触客宝迭代及部署脚本更新;未拉取远程。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 04:24:56 +08:00

205 lines
8.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* 开放对接平台 · 场景获客线索上报JWT 模式)
* POST /v1/open/scenarios
*
* 业务逻辑沿用 PostExternalApiV1Controller只是鉴权由「apiKey+sign 每次请求」改为「JWT Bearer」。
* 第三方先调 /v1/open/auth/token 拿到 token后续上报客资全部用 Bearer。
*
* 文档:开发文档/5、接口/08-存客宝开放对接平台/03-领域接口表.md §1
*
* @package app\common\controller
*/
namespace app\common\controller;
use app\common\service\OpenPlatformService;
use app\common\service\TrafficPoolSystemIdentifierService;
use app\cunkebao\service\DistributionRewardService;
use app\cunkebao\service\PlanLeadWebhookService;
use app\cunkebao\service\TrafficPoolService;
use app\common\model\TrafficPoolSource;
use library\ResponseHelper;
use think\Controller;
use think\Db;
use think\facade\Log;
use think\facade\Request;
class OpenScenariosController extends Controller
{
/**
* 线索上报JWT 模式)
*
* 入参:
* phone / wechatId (二选一)
* name / source / remark / tags(comma) / siteTags(comma) / cid(渠道ID)
*
* 出参:
* { code:200, msg:'新增成功'|'已存在', data:identifier }
*/
public function submit()
{
$ctx = OpenPlatformService::resolveContext();
if (!$ctx) {
return ResponseHelper::unauthorized('未授权或 Token 已过期');
}
$planId = $ctx['planId'];
$companyId = $ctx['companyId'];
$plan = Db::name('customer_acquisition_task')
->where('id', $planId)
->where('status', 1)
->find();
if (!$plan) {
return ResponseHelper::error('计划不存在或已停用', 404);
}
$params = Request::param();
$identifier = !empty($params['wechatId']) ? $params['wechatId'] : ($params['phone'] ?? '');
if (empty($identifier)) {
return ResponseHelper::error('phone 或 wechatId 至少传一个', 400);
}
$channelId = !empty($params['cid']) ? intval($params['cid']) : 0;
$existing = Db::name('task_customer')
->where('task_id', $planId)
->where('phone', $identifier)
->find();
if ($existing) {
// 已存在 → 仅追加标签
$siteTags = !empty($params['siteTags']) ? explode(',', $params['siteTags']) : [];
if ($siteTags) {
$this->updateSiteTags($existing['id'], $siteTags);
}
PlanLeadWebhookService::dispatch((int) $planId, (int) $existing['id'], PlanLeadWebhookService::EVENT_TAGS_UPDATED);
return ResponseHelper::success(['identifier' => $identifier, 'status' => 'existed'], '已存在');
}
// 渠道校验(沿用旧逻辑)
$finalChannelId = 0;
if ($channelId > 0) {
$sceneConf = json_decode($plan['sceneConf'] ?? '', true) ?: [];
$distributionConfig = $sceneConf['distribution'] ?? null;
$allowedChannelIds = $distributionConfig['channels'] ?? [];
if (!empty($distributionConfig) && !empty($distributionConfig['enabled']) && in_array($channelId, $allowedChannelIds)) {
$channel = Db::name('distribution_channel')
->where([
['id', '=', $channelId],
['companyId', '=', $companyId],
['status', '=', 'enabled'],
['deleteTime', '=', 0],
])
->find();
if ($channel) {
$finalChannelId = $channelId;
}
}
}
$tags = !empty($params['tags']) ? explode(',', $params['tags']) : [];
$siteTags = !empty($params['siteTags']) ? explode(',', $params['siteTags']) : [];
$customerId = Db::name('task_customer')->insertGetId([
'task_id' => $planId,
'channelId' => $finalChannelId,
'phone' => $identifier,
'name' => $params['name'] ?? '',
'source' => $params['source'] ?? '',
'remark' => $params['remark'] ?? '',
'tags' => json_encode($tags, 256),
'siteTags' => json_encode($siteTags, 256),
'createTime' => time(),
]);
if ($customerId) {
// 同步 V2 流量池(异步,失败不影响主流程)
// AI数智员工设备绑定标识aistaff_*)不是微信客资,禁止入池
$skipPool = TrafficPoolSystemIdentifierService::isExcluded($identifier)
|| TrafficPoolSystemIdentifierService::isExcludedBySiteTags($siteTags);
if (!$skipPool) {
try {
$isPhone = preg_match('/^\+?\d{6,}$/', $identifier);
$identifierType = $isPhone ? 2 : 1;
$poolService = new TrafficPoolService();
$poolService->enterPool(
$identifier,
$companyId,
TrafficPoolSource::SOURCE_TYPE_API,
[
'identifierType' => $identifierType,
'mobile' => !empty($params['phone']) ? $params['phone'] : ($isPhone ? $identifier : ''),
'wechatId' => !empty($params['wechatId']) ? $params['wechatId'] : (!$isPhone ? $identifier : ''),
'nickname' => $params['name'] ?? '',
],
[
'phone' => !empty($params['phone']) ? $params['phone'] : ($isPhone ? $identifier : ''),
'realName' => $params['name'] ?? '',
'remark' => $params['remark'] ?? '',
],
[
'sourceName' => !empty($params['source']) ? $params['source'] : ('场景获客_' . $plan['name']),
'remark' => $params['remark'] ?? '',
'extra' => json_encode([
'planId' => (int) $plan['id'],
'planName' => $plan['name'],
'channelId' => $finalChannelId,
'customerId' => $customerId,
'origin' => 'open-platform',
], JSON_UNESCAPED_UNICODE),
]
);
} catch (\Exception $e) {
Log::error('[OpenScenarios] 同步流量池失败:' . $e->getMessage());
}
}
// 分销奖励
try {
if ($finalChannelId > 0) {
DistributionRewardService::recordCustomerReward($planId, $customerId, $identifier, $finalChannelId);
}
} catch (\Exception $e) {
Log::error('[OpenScenarios] 分销奖励失败:' . $e->getMessage());
}
PlanLeadWebhookService::dispatch((int) $planId, (int) $customerId, PlanLeadWebhookService::EVENT_CREATED);
}
return ResponseHelper::success([
'identifier' => $identifier,
'customerId' => $customerId,
'status' => 'created',
], '新增成功');
}
/**
* 合并去重 siteTags
*/
private function updateSiteTags($taskCustomerId, $newSiteTags)
{
if (empty($taskCustomerId) || empty($newSiteTags) || !is_array($newSiteTags)) {
return;
}
try {
$row = Db::name('task_customer')->where('id', $taskCustomerId)->find();
if (!$row) {
return;
}
$existing = [];
if (!empty($row['siteTags'])) {
$existing = json_decode($row['siteTags'], true) ?: [];
}
$merged = array_values(array_unique(array_filter(array_merge($existing, $newSiteTags), function ($t) {
return !empty(trim((string) $t));
})));
Db::name('task_customer')->where('id', $taskCustomerId)->update([
'siteTags' => json_encode($merged, JSON_UNESCAPED_UNICODE),
'updateTime' => time(),
]);
} catch (\Exception $e) {
Log::error('[OpenScenarios] 更新 siteTags 失败:' . $e->getMessage());
}
}
}