Files
CKB-Interface/application/common/controller/OpenScenariosController.php
2026-03-24 10:39:16 +08:00

235 lines
9.2 KiB
PHP
Raw 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
namespace app\common\controller;
use think\Controller;
use think\Db;
use think\facade\Log;
use app\common\model\TrafficPoolSource;
use app\cunkebao\service\TrafficPoolService;
use app\cunkebao\service\DistributionRewardService;
/**
* 对外开放接口 — 场景获客线索上报
*
* 鉴权Bearer JWT通过 POST /v1/open/auth/token 获取)
* 路由POST /v1/open/scenarios挂 jwt 中间件)
*
* 第三方调用流程:
* 1. POST /v1/open/auth/token → 获得 JWT Token
* 2. POST /v1/open/scenarios → Header: Authorization: Bearer <token>
*/
class OpenScenariosController extends Controller
{
/**
* 线索上报入口
* POST /v1/open/scenarios
*/
public function submit()
{
try {
// ── 1. 从 JWT 中取当前用户(由 jwt 中间件注入)─────────────────
$userInfo = $this->request->userInfo ?? [];
$companyId = (int)($userInfo['companyId'] ?? 0);
if (empty($userInfo['id']) || empty($companyId)) {
return $this->error('未授权访问', 401);
}
$params = $this->request->param();
// ── 2. planId 校验(必须属于当前账号的 companyId且已启用──
if (empty($params['planId'])) {
return $this->error('planId不能为空', 400);
}
$plan = Db::name('customer_acquisition_task')
->where('id', intval($params['planId']))
->where('companyId', $companyId)
->where('status', 1)
->find();
if (!$plan) {
return $this->error('计划不存在或已停用', 404);
}
// ── 3. 主标识wechatId 优先phone 次之)─────────────────────
$wechatId = trim($params['wechatId'] ?? '');
$phone = trim($params['phone'] ?? '');
$identifier = $wechatId ?: $phone;
if (empty($identifier)) {
return $this->error('wechatId 和 phone 至少传一个', 400);
}
// ── 4. 渠道 ID可选分销场景──────────────────────────────
$channelId = !empty($params['cid']) ? intval($params['cid']) : 0;
$finalChannelId = $this->resolveChannelId($channelId, $plan);
// ── 5. 查重 & 写入 task_customer ──────────────────────────────
$taskCustomer = Db::name('task_customer')
->where('task_id', $plan['id'])
->where('phone', $identifier)
->find();
if ($taskCustomer) {
// 已存在:仅追加站内标签
if (!empty($params['siteTags'])) {
$this->mergeSiteTags(
$taskCustomer['id'],
explode(',', $params['siteTags'])
);
}
return $this->success($identifier, '已存在');
}
// 新线索
$tags = !empty($params['tags']) ? explode(',', $params['tags']) : [];
$siteTags = !empty($params['siteTags']) ? explode(',', $params['siteTags']) : [];
$customerId = Db::name('task_customer')->insertGetId([
'task_id' => $plan['id'],
'channelId' => $finalChannelId,
'phone' => $identifier,
'name' => $params['name'] ?? '',
'source' => $params['source'] ?? '',
'remark' => $params['remark'] ?? '',
'tags' => json_encode($tags, JSON_UNESCAPED_UNICODE),
'siteTags' => json_encode($siteTags, JSON_UNESCAPED_UNICODE),
'createTime' => time(),
]);
// ── 6. 同步到 V2 流量池(异步,不影响主流程)─────────────────
if ($customerId) {
$this->syncToTrafficPool(
$identifier, $phone, $wechatId,
$companyId, $plan, $params,
$finalChannelId, $customerId
);
}
// ── 7. 分销获客奖励(异步,不影响主流程)────────────────────
if ($customerId && $finalChannelId > 0) {
try {
DistributionRewardService::recordCustomerReward(
$plan['id'], $customerId, $identifier, $finalChannelId
);
} catch (\Exception $e) {
Log::error('[OpenScenarios] 记录获客奖励失败:' . $e->getMessage());
}
}
return $this->success($identifier, '新增成功');
} catch (\Exception $e) {
Log::error('[OpenScenarios] 系统错误:' . $e->getMessage() . "\n" . $e->getTraceAsString());
return $this->error('系统错误: ' . $e->getMessage(), 500);
}
}
// ─────────────────────────────────────────────────────────────────────────
private function resolveChannelId(int $channelId, array $plan): int
{
if ($channelId <= 0) {
return 0;
}
$sceneConf = json_decode($plan['sceneConf'] ?? '{}', true) ?: [];
$distConfig = $sceneConf['distribution'] ?? [];
$allowedIds = $distConfig['channels'] ?? [];
if (empty($distConfig['enabled']) || !in_array($channelId, $allowedIds)) {
return 0;
}
$channel = Db::name('distribution_channel')
->where([
['id', '=', $channelId],
['companyId', '=', $plan['companyId']],
['status', '=', 'enabled'],
['deleteTime', '=', 0],
])
->find();
return $channel ? $channelId : 0;
}
private function mergeSiteTags(int $taskCustomerId, array $newTags): void
{
try {
$row = Db::name('task_customer')->where('id', $taskCustomerId)->find();
if (!$row) {
return;
}
$existing = !empty($row['siteTags']) ? (json_decode($row['siteTags'], true) ?: []) : [];
$merged = array_values(array_unique(array_filter(
array_merge($existing, $newTags),
fn($t) => trim($t) !== ''
)));
Db::name('task_customer')->where('id', $taskCustomerId)->update([
'siteTags' => json_encode($merged, JSON_UNESCAPED_UNICODE),
'updateTime' => time(),
]);
} catch (\Exception $e) {
Log::error('[OpenScenarios] 合并站内标签失败:' . $e->getMessage());
}
}
private function syncToTrafficPool(
string $identifier,
string $phone,
string $wechatId,
int $companyId,
array $plan,
array $params,
int $finalChannelId,
int $customerId
): void {
try {
$isPhone = (bool)preg_match('/^\+?\d{6,}$/', $identifier);
$poolService = new TrafficPoolService();
$poolService->enterPool(
$identifier,
$companyId,
TrafficPoolSource::SOURCE_TYPE_API,
[
'identifierType' => $isPhone ? 2 : 1,
'mobile' => $phone ?: ($isPhone ? $identifier : ''),
'wechatId' => $wechatId ?: (!$isPhone ? $identifier : ''),
'nickname' => $params['name'] ?? '',
],
[
'phone' => $phone ?: ($isPhone ? $identifier : ''),
'realName' => $params['name'] ?? '',
'remark' => $params['remark'] ?? '',
],
[
'sourceName' => !empty($params['source'])
? $params['source']
: ('场景获客_' . $plan['name']),
'remark' => $params['remark'] ?? '',
'extra' => json_encode([
'planId' => $plan['id'],
'planName' => $plan['name'],
'channelId' => $finalChannelId,
'customerId' => $customerId,
'via' => 'open_api',
], JSON_UNESCAPED_UNICODE),
]
);
} catch (\Exception $e) {
Log::error('[OpenScenarios] 同步 V2 流量池失败:' . $e->getMessage());
}
}
private function success(string $data, string $message = 'success')
{
return json(['code' => 200, 'message' => $message, 'data' => $data]);
}
private function error(string $message, int $code = 400)
{
return json(['code' => $code, 'message' => $message, 'data' => null]);
}
}