205 lines
8.2 KiB
PHP
205 lines
8.2 KiB
PHP
<?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());
|
||
}
|
||
}
|
||
}
|