新版数智员工
This commit is contained in:
234
application/common/controller/OpenScenariosController.php
Normal file
234
application/common/controller/OpenScenariosController.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user