存客宝应用接口初始化
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\common\model\Device as DeviceModel;
|
||||
use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel;
|
||||
use app\common\model\WechatCustomer as WechatCustomerModel;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 获取获客计划详情控制器
|
||||
*/
|
||||
class GetAddFriendPlanDetailV1Controller extends Controller
|
||||
{
|
||||
/**
|
||||
* 生成签名
|
||||
*
|
||||
* @param array $params 参数数组
|
||||
* @param string $apiKey API密钥
|
||||
* @return string
|
||||
*/
|
||||
private function generateSignature($params, $apiKey)
|
||||
{
|
||||
// 1. 移除sign和apiKey
|
||||
unset($params['sign'], $params['apiKey']);
|
||||
|
||||
// 2. 移除空值
|
||||
$params = array_filter($params, function($value) {
|
||||
return !is_null($value) && $value !== '';
|
||||
});
|
||||
|
||||
// 3. 参数按键名升序排序
|
||||
ksort($params);
|
||||
|
||||
// 4. 直接拼接参数值
|
||||
$stringToSign = implode('', array_values($params));
|
||||
|
||||
// 5. 第一次MD5加密
|
||||
$firstMd5 = md5($stringToSign);
|
||||
|
||||
// 6. 拼接apiKey并第二次MD5加密
|
||||
return md5($firstMd5 . $apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成测试URL
|
||||
*
|
||||
* @param string $apiKey API密钥
|
||||
* @return array
|
||||
*/
|
||||
public function testUrl($apiKey)
|
||||
{
|
||||
try {
|
||||
if (empty($apiKey)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 构建测试参数
|
||||
$testParams = [
|
||||
'name' => '测试客户',
|
||||
'phone' => '18888888888',
|
||||
'apiKey' => $apiKey,
|
||||
'timestamp' => time()
|
||||
];
|
||||
|
||||
// 生成签名
|
||||
$sign = $this->generateSignature($testParams, $apiKey);
|
||||
$testParams['sign'] = $sign;
|
||||
|
||||
// 构建签名过程说明
|
||||
$signParams = $testParams;
|
||||
unset($signParams['sign'], $signParams['apiKey']);
|
||||
ksort($signParams);
|
||||
$signStr = implode('', array_values($signParams));
|
||||
|
||||
// 构建完整URL参数,不对中文进行编码
|
||||
$urlParams = [];
|
||||
foreach ($testParams as $key => $value) {
|
||||
$urlParams[] = $key . '=' . $value;
|
||||
}
|
||||
$fullUrl = implode('&', $urlParams);
|
||||
|
||||
return [
|
||||
'apiKey' => $apiKey,
|
||||
'originalString' => $signStr,
|
||||
'sign' => $sign,
|
||||
'fullUrl' => $fullUrl
|
||||
];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$planId = $this->request->param('planId');
|
||||
|
||||
if (empty($planId)) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 查询计划详情
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('id', $planId)
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
// 解析JSON字段
|
||||
$sceneConf = json_decode($plan['sceneConf'], true) ?: [];
|
||||
$reqConf = json_decode($plan['reqConf'], true) ?: [];
|
||||
$reqConf['deviceGroups'] = $reqConf['device'];
|
||||
$msgConf = json_decode($plan['msgConf'], true) ?: [];
|
||||
$tagConf = json_decode($plan['tagConf'], true) ?: [];
|
||||
|
||||
// 处理分销配置
|
||||
$distributionConfig = $sceneConf['distribution'] ?? [
|
||||
'enabled' => false,
|
||||
'channels' => [],
|
||||
'customerRewardAmount' => 0,
|
||||
'addFriendRewardAmount' => 0,
|
||||
];
|
||||
|
||||
// 格式化分销配置(分转元,并获取渠道详情)
|
||||
$distributionEnabled = !empty($distributionConfig['enabled']);
|
||||
$distributionChannels = [];
|
||||
if ($distributionEnabled && !empty($distributionConfig['channels'])) {
|
||||
$channels = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', 'in', $distributionConfig['channels']],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->field('id,code,name')
|
||||
->select();
|
||||
$distributionChannels = array_map(function($channel) {
|
||||
return [
|
||||
'id' => (int)$channel['id'],
|
||||
'code' => $channel['code'],
|
||||
'name' => $channel['name']
|
||||
];
|
||||
}, $channels);
|
||||
}
|
||||
|
||||
// 将分销配置添加到返回数据中
|
||||
$sceneConf['distributionEnabled'] = $distributionEnabled;
|
||||
$sceneConf['distributionChannels'] = $distributionChannels;
|
||||
$sceneConf['customerRewardAmount'] = round(($distributionConfig['customerRewardAmount'] ?? 0) / 100, 2); // 分转元
|
||||
$sceneConf['addFriendRewardAmount'] = round(($distributionConfig['addFriendRewardAmount'] ?? 0) / 100, 2); // 分转元
|
||||
|
||||
|
||||
|
||||
if(!empty($sceneConf['wechatGroups'])){
|
||||
$groupList = Db::name('wechat_group')->alias('wg')
|
||||
->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId')
|
||||
->where('wg.id', 'in', $sceneConf['wechatGroups'])
|
||||
->order('wg.id', 'desc')
|
||||
->field('wg.id,wg.name,wg.chatroomId,wg.ownerWechatId,wa.nickName as ownerNickName,wa.avatar as ownerAvatar,wa.alias as ownerAlias,wg.avatar')
|
||||
->select();
|
||||
$sceneConf['wechatGroupsOptions'] = $groupList;
|
||||
}else{
|
||||
$sceneConf['wechatGroupsOptions'] = [];
|
||||
}
|
||||
|
||||
|
||||
if (!empty($reqConf['deviceGroups'])){
|
||||
$deviceGroupsOptions = DeviceModel::alias('d')
|
||||
->field([
|
||||
'd.id', 'd.imei', 'd.memo', 'd.alive',
|
||||
'l.wechatId',
|
||||
'a.nickname', 'a.alias', '0 totalFriend', '0 totalFriend'
|
||||
])
|
||||
->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId')
|
||||
->leftJoin('wechat_account a', 'l.wechatId = a.wechatId')
|
||||
->order('d.id desc')
|
||||
->whereIn('d.id',$reqConf['deviceGroups'])
|
||||
->select();
|
||||
foreach ($deviceGroupsOptions as &$device) {
|
||||
$curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find();
|
||||
$device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0;
|
||||
}
|
||||
unset($device);
|
||||
$reqConf['deviceGroupsOptions'] = $deviceGroupsOptions;
|
||||
}else{
|
||||
$reqConf['deviceGroupsOptions'] = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
unset(
|
||||
$reqConf['device'],
|
||||
$sceneConf['groupSelected'],
|
||||
);
|
||||
|
||||
// 合并数据
|
||||
$newData['messagePlans'] = $msgConf;
|
||||
$newData = array_merge($newData, $sceneConf, $reqConf, $tagConf, $plan);
|
||||
|
||||
// 移除不需要的字段
|
||||
unset(
|
||||
$newData['sceneConf'],
|
||||
$newData['reqConf'],
|
||||
$newData['msgConf'],
|
||||
$newData['tagConf'],
|
||||
$newData['userInfo'],
|
||||
$newData['createTime'],
|
||||
$newData['updateTime'],
|
||||
$newData['deleteTime']
|
||||
);
|
||||
|
||||
// 生成测试URL
|
||||
$newData['textUrl'] = $this->testUrl($newData['apiKey']);
|
||||
|
||||
return ResponseHelper::success($newData, '获取计划详情成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
|
||||
/**
|
||||
* 获取计划任务列表控制器
|
||||
*/
|
||||
class GetCreateAddFriendPlanV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 生成唯一API密钥
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function generateApiKey()
|
||||
{
|
||||
// 生成6组随机字符串,每组5个字符
|
||||
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
$apiKey = '';
|
||||
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$segment = '';
|
||||
for ($j = 0; $j < 5; $j++) {
|
||||
$segment .= $chars[mt_rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
$apiKey .= ($i > 0 ? '-' : '') . $segment;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
$exists = Db::name('customer_acquisition_task')
|
||||
->where('apiKey', $apiKey)
|
||||
->find();
|
||||
|
||||
if ($exists) {
|
||||
// 如果已存在,递归重新生成
|
||||
return $this->generateApiKey();
|
||||
}
|
||||
|
||||
return $apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function copy()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$plan = Db::name('customer_acquisition_task')->where('id', $planId)->find();
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
unset($plan['id']);
|
||||
$plan['name'] = $plan['name'] . ' (拷贝)';
|
||||
$plan['createTime'] = time();
|
||||
$plan['updateTime'] = time();
|
||||
$plan['apiKey'] = $this->generateApiKey(); // 生成新的API密钥
|
||||
|
||||
$newPlanId = Db::name('customer_acquisition_task')->insertGetId($plan);
|
||||
if (!$newPlanId) {
|
||||
return ResponseHelper::error('拷贝计划失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success(['planId' => $newPlanId], '拷贝计划任务成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['deleteTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('删除计划失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '删除计划任务成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改计划任务状态
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateStatus()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
$status = isset($params['status']) ? intval($params['status']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['status' => $status, 'updateTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('修改计划状态失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '修改计划任务状态成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\common\model\PlanScene as PlansSceneModel;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 获客场景控制器
|
||||
*/
|
||||
class GetPlanSceneListV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取开启的场景列表
|
||||
*
|
||||
* @param array $params 查询参数
|
||||
* @return array
|
||||
*/
|
||||
protected function getSceneList(array $params = []): array
|
||||
{
|
||||
try {
|
||||
// 构建查询条件
|
||||
$where = ['status' => PlansSceneModel::STATUS_ACTIVE];
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$where[] = ['name', 'like', '%' . $params['keyword'] . '%'];
|
||||
}
|
||||
|
||||
// 标签筛选
|
||||
if (!empty($params['tag'])) {
|
||||
$where[] = ['scenarioTags', 'like', '%' . $params['tag'] . '%'];
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
$query = PlansSceneModel::where($where);
|
||||
|
||||
// 获取分页数据
|
||||
$list = $query->order('sort DESC')->select()->toArray();
|
||||
|
||||
if (empty($list)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$sceneIds = array_column($list, 'id');
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$statsMap = $this->buildSceneStats($sceneIds, (int)$companyId);
|
||||
|
||||
// 处理数据
|
||||
foreach($list as &$val) {
|
||||
$val['scenarioTags'] = json_decode($val['scenarioTags'], true) ?: [];
|
||||
$sceneStats = $statsMap[$val['id']] ?? ['count' => 0, 'growth' => '0%'];
|
||||
$val['count'] = $sceneStats['count'];
|
||||
$val['growth'] = $sceneStats['growth'];
|
||||
}
|
||||
unset($val);
|
||||
|
||||
return $list;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('获取场景列表失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$result = $this->getSceneList($params);
|
||||
return ResponseHelper::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景详情
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id', '');
|
||||
if(empty($id)) {
|
||||
return ResponseHelper::error('参数缺失');
|
||||
}
|
||||
|
||||
$data = PlansSceneModel::where([
|
||||
'status' => PlansSceneModel::STATUS_ACTIVE,
|
||||
'id' => $id
|
||||
])->find();
|
||||
|
||||
if(empty($data)) {
|
||||
return ResponseHelper::error('场景不存在');
|
||||
}
|
||||
|
||||
$data['scenarioTags'] = json_decode($data['scenarioTags'], true) ?: [];
|
||||
$data['count'] = $this->getPlanCount($id);
|
||||
$data['growth'] = $this->calculateGrowth($id);
|
||||
|
||||
return ResponseHelper::success($data);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error($e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划数量
|
||||
*
|
||||
* @param int $sceneId 场景ID
|
||||
* @return int
|
||||
*/
|
||||
private function getPlanCount(int $sceneId): int
|
||||
{
|
||||
return Db::name('customer_acquisition_task')
|
||||
->where('sceneId', $sceneId)
|
||||
->where('companyId',$this->getUserInfo('companyId'))
|
||||
->where('deleteTime', 0)
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算增长率
|
||||
*
|
||||
* @param int $sceneId 场景ID
|
||||
* @return string
|
||||
*/
|
||||
private function calculateGrowth(int $sceneId): string
|
||||
{
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
$currentStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$nextMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('+1 month')));
|
||||
$lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month')));
|
||||
|
||||
$currentMonth = $this->getSceneMonthlyCount($sceneId, $companyId, $currentStart, $nextMonthStart - 1);
|
||||
$lastMonth = $this->getSceneMonthlyCount($sceneId, $companyId, $lastMonthStart, $currentStart - 1);
|
||||
|
||||
return $this->formatGrowthPercentage($currentMonth, $lastMonth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量构建场景统计数据
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
private function buildSceneStats(array $sceneIds, int $companyId): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$totalCounts = $this->getSceneTaskCounts($sceneIds, $companyId);
|
||||
|
||||
$currentStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$nextMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('+1 month')));
|
||||
$lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month')));
|
||||
|
||||
$currentMonthCounts = $this->getSceneMonthlyCounts($sceneIds, $companyId, $currentStart, $nextMonthStart - 1);
|
||||
$lastMonthCounts = $this->getSceneMonthlyCounts($sceneIds, $companyId, $lastMonthStart, $currentStart - 1);
|
||||
|
||||
$stats = [];
|
||||
foreach ($sceneIds as $sceneId) {
|
||||
$current = $currentMonthCounts[$sceneId] ?? 0;
|
||||
$last = $lastMonthCounts[$sceneId] ?? 0;
|
||||
$stats[$sceneId] = [
|
||||
'count' => $totalCounts[$sceneId] ?? 0,
|
||||
'growth' => $this->formatGrowthPercentage($current, $last),
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景计划总数
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @return array
|
||||
*/
|
||||
private function getSceneTaskCounts(array $sceneIds, int $companyId): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
$where = [
|
||||
['companyId', '=', $companyId],
|
||||
['deleteTime', '=', 0],
|
||||
['sceneId', 'in', $sceneIds],
|
||||
];
|
||||
if(!$this->getUserInfo('isAdmin')){
|
||||
$where[] = ['userId', '=', $this->getUserInfo('id')];
|
||||
}
|
||||
|
||||
|
||||
$rows = Db::name('customer_acquisition_task')
|
||||
->where($where)
|
||||
->field('sceneId, COUNT(*) as total')
|
||||
->group('sceneId')
|
||||
->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$result[$sceneId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取场景月度数据
|
||||
* @param array $sceneIds
|
||||
* @param int $companyId
|
||||
* @param int $startTime
|
||||
* @param int $endTime
|
||||
* @return array
|
||||
*/
|
||||
private function getSceneMonthlyCounts(array $sceneIds, int $companyId, int $startTime, int $endTime): array
|
||||
{
|
||||
if (empty($sceneIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('customer_acquisition_task')
|
||||
->whereIn('sceneId', $sceneIds)
|
||||
->where('companyId', $companyId)
|
||||
->where('status', 1)
|
||||
->where('deleteTime', 0)
|
||||
->whereBetween('createTime', [$startTime, $endTime])
|
||||
->field('sceneId, COUNT(*) as total')
|
||||
->group('sceneId')
|
||||
->select();
|
||||
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$sceneId = is_array($row) ? ($row['sceneId'] ?? 0) : ($row->sceneId ?? 0);
|
||||
if (!$sceneId) {
|
||||
continue;
|
||||
}
|
||||
$result[$sceneId] = (int)(is_array($row) ? ($row['total'] ?? 0) : ($row->total ?? 0));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个场景的月度数据
|
||||
* @param int $sceneId
|
||||
* @param int $companyId
|
||||
* @param int $startTime
|
||||
* @param int $endTime
|
||||
* @return int
|
||||
*/
|
||||
private function getSceneMonthlyCount(int $sceneId, int $companyId, int $startTime, int $endTime): int
|
||||
{
|
||||
return Db::name('customer_acquisition_task')
|
||||
->where('sceneId', $sceneId)
|
||||
->where('companyId', $companyId)
|
||||
->where('status', 1)
|
||||
->where('deleteTime', 0)
|
||||
->whereBetween('createTime', [$startTime, $endTime])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算增长百分比
|
||||
* @param int $current
|
||||
* @param int $last
|
||||
* @return string
|
||||
*/
|
||||
private function formatGrowthPercentage(int $current, int $last): string
|
||||
{
|
||||
if ($last == 0) {
|
||||
return $current > 0 ? '100%' : '0%';
|
||||
}
|
||||
|
||||
$growth = round(($current - $last) / $last * 100, 2);
|
||||
return $growth . '%';
|
||||
}
|
||||
}
|
||||
554
application/cunkebao/controller/plan/PlanSceneV1Controller.php
Normal file
554
application/cunkebao/controller/plan/PlanSceneV1Controller.php
Normal file
@@ -0,0 +1,554 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use app\cunkebao\controller\plan\PosterWeChatMiniProgram;
|
||||
|
||||
/**
|
||||
* 获取计划任务列表控制器
|
||||
*/
|
||||
class PlanSceneV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取计划任务列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$page = isset($params['page']) ? intval($params['page']) : 1;
|
||||
$limit = isset($params['limit']) ? intval($params['limit']) : 10;
|
||||
$keyword = isset($params['keyword']) ? trim($params['keyword']) : '';
|
||||
$sceneId = $this->request->param('sceneId','');
|
||||
$where = [
|
||||
'deleteTime' => 0,
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
];
|
||||
|
||||
if(!$this->getUserInfo('isAdmin')){
|
||||
$where['userId'] = $this->getUserInfo('id');
|
||||
}
|
||||
|
||||
if(!empty($sceneId)){
|
||||
$where['sceneId'] = $sceneId;
|
||||
}
|
||||
|
||||
if(!empty($keyword)){
|
||||
$where[] = ['name', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
|
||||
$total = Db::name('customer_acquisition_task')->where($where)->count();
|
||||
$list = Db::name('customer_acquisition_task')
|
||||
->where($where)
|
||||
->order('createTime', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
if (!empty($list)) {
|
||||
$taskIds = array_column($list, 'id');
|
||||
$statsMap = $this->buildTaskStats($taskIds);
|
||||
|
||||
foreach($list as &$val){
|
||||
$val['createTime'] = !empty($val['createTime']) ? date('Y-m-d H:i:s', $val['createTime']) : '';
|
||||
$val['updateTime'] = !empty($val['updateTime']) ? date('Y-m-d H:i:s', $val['updateTime']) : '';
|
||||
$val['sceneConf'] = json_decode($val['sceneConf'],true) ?: [];
|
||||
$val['reqConf'] = json_decode($val['reqConf'],true) ?: [];
|
||||
$val['msgConf'] = json_decode($val['msgConf'],true) ?: [];
|
||||
$val['tagConf'] = json_decode($val['tagConf'],true) ?: [];
|
||||
|
||||
$stats = $statsMap[$val['id']] ?? [
|
||||
'acquiredCount' => 0,
|
||||
'addedCount' => 0,
|
||||
'passCount' => 0,
|
||||
'lastUpdated' => 0
|
||||
];
|
||||
|
||||
$val['acquiredCount'] = $stats['acquiredCount'];
|
||||
$val['addedCount'] = $stats['addedCount'];
|
||||
$val['passCount'] = $stats['passCount'];
|
||||
$val['passRate'] = ($stats['addedCount'] > 0 && $stats['passCount'] > 0)
|
||||
? number_format(($stats['passCount'] / $stats['addedCount']) * 100, 2)
|
||||
: 0;
|
||||
$val['lastUpdated'] = !empty($stats['lastUpdated']) ? date('Y-m-d H:i', $stats['lastUpdated']) : '--';
|
||||
}
|
||||
unset($val);
|
||||
}
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list
|
||||
], '获取计划任务列表成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['deleteTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('删除计划失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '删除计划任务成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改计划任务状态
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateStatus()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
$status = isset($params['status']) ? intval($params['status']) : 0;
|
||||
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
$result = Db::name('customer_acquisition_task')->where('id', $planId)->update(['status' => $status, 'updateTime' => time()]);
|
||||
if (!$result) {
|
||||
return ResponseHelper::error('修改计划状态失败', 500);
|
||||
}
|
||||
|
||||
return ResponseHelper::success([], '修改计划任务状态成功');
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取获客计划设备列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getPlanDevices()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
$planId = isset($params['planId']) ? intval($params['planId']) : 0;
|
||||
$page = isset($params['page']) ? intval($params['page']) : 1;
|
||||
$limit = isset($params['limit']) ? intval($params['limit']) : 10;
|
||||
$deviceStatus = isset($params['deviceStatus']) ? $params['deviceStatus'] : '';
|
||||
$searchKeyword = isset($params['searchKeyword']) ? trim($params['searchKeyword']) : '';
|
||||
|
||||
// 验证计划ID
|
||||
if ($planId <= 0) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 验证计划是否存在且用户有权限
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where([
|
||||
'id' => $planId,
|
||||
'deleteTime' => 0,
|
||||
'companyId' => $this->getUserInfo('companyId')
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在或无权限访问', 404);
|
||||
}
|
||||
|
||||
// 如果是管理员,需要验证用户权限
|
||||
if (!$this->getUserInfo('isAdmin')) {
|
||||
$userPlan = Db::name('customer_acquisition_task')
|
||||
->where([
|
||||
'id' => $planId,
|
||||
'userId' => $this->getUserInfo('id')
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$userPlan) {
|
||||
return ResponseHelper::error('您没有权限访问该计划', 403);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
'pt.plan_id' => $planId,
|
||||
'd.deleteTime' => 0,
|
||||
'd.companyId' => $this->getUserInfo('companyId')
|
||||
];
|
||||
|
||||
// 设备状态筛选
|
||||
if (!empty($deviceStatus)) {
|
||||
$where['d.alive'] = $deviceStatus;
|
||||
}
|
||||
|
||||
// 搜索关键词
|
||||
$searchWhere = [];
|
||||
if (!empty($searchKeyword)) {
|
||||
$searchWhere[] = ['d.imei', 'like', "%{$searchKeyword}%"];
|
||||
$searchWhere[] = ['d.memo', 'like', "%{$searchKeyword}%"];
|
||||
}
|
||||
|
||||
// 查询设备总数
|
||||
$totalQuery = Db::name('plan_task_device')->alias('pt')
|
||||
->join('device d', 'pt.device_id = d.id')
|
||||
->where($where);
|
||||
|
||||
if (!empty($searchWhere)) {
|
||||
$totalQuery->where(function ($query) use ($searchWhere) {
|
||||
foreach ($searchWhere as $condition) {
|
||||
$query->whereOr($condition[0], $condition[1], $condition[2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$total = $totalQuery->count();
|
||||
|
||||
// 查询设备列表
|
||||
$listQuery = Db::name('plan_task_device')->alias('pt')
|
||||
->join('device d', 'pt.device_id = d.id')
|
||||
->field([
|
||||
'd.id',
|
||||
'd.imei',
|
||||
'd.memo',
|
||||
'd.alive',
|
||||
'd.extra',
|
||||
'd.createTime',
|
||||
'd.updateTime',
|
||||
'pt.status as plan_device_status',
|
||||
'pt.createTime as assign_time'
|
||||
])
|
||||
->where($where)
|
||||
->order('pt.createTime', 'desc');
|
||||
|
||||
if (!empty($searchWhere)) {
|
||||
$listQuery->where(function ($query) use ($searchWhere) {
|
||||
foreach ($searchWhere as $condition) {
|
||||
$query->whereOr($condition[0], $condition[1], $condition[2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$list = $listQuery->page($page, $limit)->select();
|
||||
|
||||
// 处理设备数据
|
||||
foreach ($list as &$device) {
|
||||
// 格式化时间
|
||||
$device['createTime'] = date('Y-m-d H:i:s', $device['createTime']);
|
||||
$device['updateTime'] = date('Y-m-d H:i:s', $device['updateTime']);
|
||||
$device['assign_time'] = date('Y-m-d H:i:s', $device['assign_time']);
|
||||
|
||||
// 解析设备额外信息
|
||||
if (!empty($device['extra'])) {
|
||||
$extra = json_decode($device['extra'], true);
|
||||
$device['battery'] = isset($extra['battery']) ? intval($extra['battery']) : 0;
|
||||
$device['device_info'] = $extra;
|
||||
} else {
|
||||
$device['battery'] = 0;
|
||||
$device['device_info'] = [];
|
||||
}
|
||||
|
||||
// 设备状态文本
|
||||
$device['alive_text'] = $this->getDeviceStatusText($device['alive']);
|
||||
$device['plan_device_status_text'] = $this->getPlanDeviceStatusText($device['plan_device_status']);
|
||||
|
||||
// 获取设备当前微信登录信息
|
||||
$wechatLogin = Db::name('device_wechat_login')
|
||||
->where([
|
||||
'deviceId' => $device['id'],
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'alive' => 1
|
||||
])
|
||||
->order('createTime', 'desc')
|
||||
->find();
|
||||
|
||||
$device['current_wechat'] = $wechatLogin ? [
|
||||
'wechatId' => $wechatLogin['wechatId'],
|
||||
'nickname' => $wechatLogin['nickname'] ?? '',
|
||||
'loginTime' => date('Y-m-d H:i:s', $wechatLogin['createTime'])
|
||||
] : null;
|
||||
|
||||
// 获取设备在该计划中的任务统计
|
||||
$device['task_stats'] = $this->getDeviceTaskStats($device['id'], $planId);
|
||||
|
||||
// 移除原始extra字段
|
||||
unset($device['extra']);
|
||||
}
|
||||
unset($device);
|
||||
|
||||
return ResponseHelper::success([
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
'plan_info' => [
|
||||
'id' => $plan['id'],
|
||||
'name' => $plan['name'],
|
||||
'status' => $plan['status']
|
||||
]
|
||||
], '获取计划设备列表成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备状态文本
|
||||
*
|
||||
* @param int $status
|
||||
* @return string
|
||||
*/
|
||||
private function getDeviceStatusText($status)
|
||||
{
|
||||
$statusMap = [
|
||||
0 => '离线',
|
||||
1 => '在线',
|
||||
2 => '忙碌',
|
||||
3 => '故障'
|
||||
];
|
||||
return isset($statusMap[$status]) ? $statusMap[$status] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划设备状态文本
|
||||
*
|
||||
* @param int $status
|
||||
* @return string
|
||||
*/
|
||||
private function getPlanDeviceStatusText($status)
|
||||
{
|
||||
$statusMap = [
|
||||
0 => '待分配',
|
||||
1 => '已分配',
|
||||
2 => '执行中',
|
||||
3 => '已完成',
|
||||
4 => '已暂停',
|
||||
5 => '已取消'
|
||||
];
|
||||
return isset($statusMap[$status]) ? $statusMap[$status] : '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备在指定计划中的任务统计
|
||||
*
|
||||
* @param int $deviceId
|
||||
* @param int $planId
|
||||
* @return array
|
||||
*/
|
||||
private function getDeviceTaskStats($deviceId, $planId)
|
||||
{
|
||||
// 获取该设备在计划中的任务总数
|
||||
$totalTasks = Db::name('task_customer')
|
||||
->where([
|
||||
'task_id' => $planId,
|
||||
'device_id' => $deviceId
|
||||
])
|
||||
->count();
|
||||
|
||||
// 获取已完成的任务数
|
||||
$completedTasks = Db::name('task_customer')
|
||||
->where([
|
||||
'task_id' => $planId,
|
||||
'device_id' => $deviceId,
|
||||
'status' => 4
|
||||
])
|
||||
->count();
|
||||
|
||||
// 获取进行中的任务数
|
||||
$processingTasks = Db::name('task_customer')
|
||||
->where([
|
||||
'task_id' => $planId,
|
||||
'device_id' => $deviceId,
|
||||
'status' => ['in', [1, 2, 3]]
|
||||
])
|
||||
->count();
|
||||
|
||||
return [
|
||||
'total_tasks' => $totalTasks,
|
||||
'completed_tasks' => $completedTasks,
|
||||
'processing_tasks' => $processingTasks,
|
||||
'completion_rate' => $totalTasks > 0 ? round(($completedTasks / $totalTasks) * 100, 2) : 0
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建任务统计
|
||||
* @param array $taskIds
|
||||
* @return array
|
||||
*/
|
||||
private function buildTaskStats(array $taskIds): array
|
||||
{
|
||||
if (empty($taskIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$rows = Db::name('task_customer')
|
||||
->whereIn('task_id', $taskIds)
|
||||
->field([
|
||||
'task_id as taskId',
|
||||
'COUNT(1) as acquiredCount',
|
||||
"SUM(CASE WHEN status IN (1,2,3,4,5) THEN 1 ELSE 0 END) as addedCount",
|
||||
"SUM(CASE WHEN status IN (4,5) THEN 1 ELSE 0 END) as passCount",
|
||||
'MAX(updateTime) as lastUpdated'
|
||||
])
|
||||
->group('task_id')
|
||||
->select();
|
||||
|
||||
$stats = [];
|
||||
foreach ($rows as $row) {
|
||||
$taskId = is_array($row) ? ($row['taskId'] ?? 0) : ($row->taskId ?? 0);
|
||||
if (!$taskId) {
|
||||
continue;
|
||||
}
|
||||
$stats[$taskId] = [
|
||||
'acquiredCount' => (int)(is_array($row) ? ($row['acquiredCount'] ?? 0) : ($row->acquiredCount ?? 0)),
|
||||
'addedCount' => (int)(is_array($row) ? ($row['addedCount'] ?? 0) : ($row->addedCount ?? 0)),
|
||||
'passCount' => (int)(is_array($row) ? ($row['passCount'] ?? 0) : ($row->passCount ?? 0)),
|
||||
'lastUpdated' => (int)(is_array($row) ? ($row['lastUpdated'] ?? 0) : ($row->lastUpdated ?? 0)),
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取微信小程序码
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getWxMinAppCode()
|
||||
{
|
||||
$params = $this->request->param();
|
||||
$taskId = isset($params['taskId']) ? intval($params['taskId']) : 0;
|
||||
$channelId = isset($params['channelId']) ? intval($params['channelId']) : 0;
|
||||
|
||||
if($taskId <= 0) {
|
||||
return ResponseHelper::error('任务ID或场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
$task = Db::name('customer_acquisition_task')->where(['id' => $taskId, 'deleteTime' => 0])->find();
|
||||
if(!$task) {
|
||||
return ResponseHelper::error('任务不存在', 400);
|
||||
}
|
||||
|
||||
// 如果提供了channelId,验证渠道是否存在且有效
|
||||
if ($channelId > 0) {
|
||||
$channel = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', '=', $channelId],
|
||||
['companyId', '=', $task['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (!$channel) {
|
||||
return ResponseHelper::error('分销渠道不存在或已被禁用', 400);
|
||||
}
|
||||
}
|
||||
|
||||
$posterWeChatMiniProgram = new PosterWeChatMiniProgram();
|
||||
$result = $posterWeChatMiniProgram->generateMiniProgramCodeWithScene($taskId, $channelId);
|
||||
$result = json_decode($result, true);
|
||||
if ($result['code'] == 200){
|
||||
return ResponseHelper::success($result['data'], '获取小程序码成功');
|
||||
}else{
|
||||
return ResponseHelper::error('获取小程序失败:' . $result['msg']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取已获客/已添加用户
|
||||
* @return \think\response\Json
|
||||
* @throws \think\db\exception\DataNotFoundException
|
||||
* @throws \think\db\exception\ModelNotFoundException
|
||||
* @throws \think\exception\DbException
|
||||
*/
|
||||
public function getUserList(){
|
||||
$type = $this->request->param('type',1);
|
||||
$planId = $this->request->param('planId','');
|
||||
$page = $this->request->param('page',1);
|
||||
$pageSize = $this->request->param('pageSize',10);
|
||||
$keyword = $this->request->param('keyword','');
|
||||
|
||||
if (!in_array($type, [1, 2])) {
|
||||
return ResponseHelper::error('类型错误');
|
||||
}
|
||||
|
||||
if (empty($planId)){
|
||||
return ResponseHelper::error('获客场景id不能为空');
|
||||
}
|
||||
|
||||
$task = Db::name('customer_acquisition_task')
|
||||
->where(['id' => $planId, 'deleteTime' => 0,'companyId' => $this->getUserInfo('companyId')])
|
||||
->find();
|
||||
if(empty($task)) {
|
||||
return ResponseHelper::error('活动不存在');
|
||||
}
|
||||
$query = Db::name('task_customer')->where(['task_id' => $task['id']]);
|
||||
|
||||
if ($type == 2){
|
||||
$query = $query->whereIn('status',[4,5]);
|
||||
}
|
||||
|
||||
if (!empty($keyword)) {
|
||||
$query = $query->where('name|phone|tags|siteTags', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $pageSize)->order('id', 'desc')->select();
|
||||
foreach ($list as &$item) {
|
||||
unset($item['processed_wechat_ids'],$item['task_id']);
|
||||
$userinfo = Db::table('s2_wechat_friend')
|
||||
->field('alias,wechatId,nickname,avatar')
|
||||
->where('alias|wechatId|phone|conRemark','like','%'.$item['phone'].'%')
|
||||
->order('id DESC')
|
||||
->find();
|
||||
|
||||
if (!empty($userinfo)) {
|
||||
$item['userinfo'] = $userinfo;
|
||||
}else{
|
||||
$item['userinfo'] = [];
|
||||
}
|
||||
|
||||
$item['tags'] = json_decode($item['tags'], true);
|
||||
$item['siteTags'] = json_decode($item['siteTags'], true);
|
||||
$item['createTime'] = !empty($item['createTime']) ? date('Y-m-d H:i:s', $item['createTime']) : '';
|
||||
$item['updateTime'] = !empty($item['updateTime']) ? date('Y-m-d H:i:s', $item['updateTime']) : '';
|
||||
}
|
||||
|
||||
|
||||
|
||||
$data = [
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
];
|
||||
return ResponseHelper::success($data,'获取成功');
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
|
||||
/**
|
||||
* 获客场景控制器
|
||||
*/
|
||||
class PostCreateAddFriendPlanV1Controller extends BaseController
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* 生成唯一API密钥
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateApiKey()
|
||||
{
|
||||
// 生成5组随机字符串,每组5个字符
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
$apiKey = '';
|
||||
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$segment = '';
|
||||
for ($j = 0; $j < 5; $j++) {
|
||||
$segment .= $chars[mt_rand(0, strlen($chars) - 1)];
|
||||
}
|
||||
$apiKey .= ($i > 0 ? '-' : '') . $segment;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
$exists = Db::name('customer_acquisition_task')
|
||||
->where('apiKey', $apiKey)
|
||||
->find();
|
||||
|
||||
if ($exists) {
|
||||
// 如果已存在,递归重新生成
|
||||
return $this->generateApiKey();
|
||||
}
|
||||
|
||||
return $apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($params['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['sceneId'])) {
|
||||
return ResponseHelper::error('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['deviceGroups'])) {
|
||||
return ResponseHelper::error('请选择设备', 400);
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 处理分销配置
|
||||
$distributionConfig = $this->processDistributionConfig($params, $companyId);
|
||||
|
||||
// 归类参数
|
||||
$msgConf = isset($params['messagePlans']) ? $params['messagePlans'] : [];
|
||||
$tagConf = [
|
||||
'scenarioTags' => $params['scenarioTags'] ?? [],
|
||||
'customTags' => $params['customTags'] ?? [],
|
||||
];
|
||||
$reqConf = [
|
||||
'device' => $params['deviceGroups'] ?? [],
|
||||
'remarkType' => $params['remarkType'] ?? '',
|
||||
'greeting' => $params['greeting'] ?? '',
|
||||
'addFriendInterval' => $params['addFriendInterval'] ?? '',
|
||||
'startTime' => $params['startTime'] ?? '',
|
||||
'endTime' => $params['endTime'] ?? '',
|
||||
];
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['id'],
|
||||
$sceneConf['apiKey'],
|
||||
$sceneConf['userId'],
|
||||
$sceneConf['status'],
|
||||
$sceneConf['planId'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
$sceneConf['messagePlans'],
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
$sceneConf['device'],
|
||||
$sceneConf['orderTableFileName'],
|
||||
$sceneConf['userInfo'],
|
||||
$sceneConf['textUrl'],
|
||||
$sceneConf['remarkType'],
|
||||
$sceneConf['greeting'],
|
||||
$sceneConf['addFriendInterval'],
|
||||
$sceneConf['startTime'],
|
||||
$sceneConf['orderTableFile'],
|
||||
$sceneConf['endTime'],
|
||||
$sceneConf['distributionEnabled'],
|
||||
$sceneConf['distributionChannels'],
|
||||
$sceneConf['customerRewardAmount'],
|
||||
$sceneConf['addFriendRewardAmount']
|
||||
);
|
||||
|
||||
// 将分销配置添加到sceneConf中
|
||||
$sceneConf['distribution'] = $distributionConfig;
|
||||
|
||||
// 构建数据
|
||||
$data = [
|
||||
'name' => $params['name'],
|
||||
'sceneId' => $params['sceneId'],
|
||||
'sceneConf' => json_encode($sceneConf, JSON_UNESCAPED_UNICODE),
|
||||
'reqConf' => json_encode($reqConf, JSON_UNESCAPED_UNICODE),
|
||||
'msgConf' => json_encode($msgConf, JSON_UNESCAPED_UNICODE),
|
||||
'tagConf' => json_encode($tagConf, JSON_UNESCAPED_UNICODE),
|
||||
'userId' => $this->getUserInfo('id'),
|
||||
'companyId' => $this->getUserInfo('companyId'),
|
||||
'status' => !empty($params['status']) ? 1 : 0,
|
||||
'apiKey' => $this->generateApiKey(), // 生成API密钥
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
|
||||
try {
|
||||
Db::startTrans();
|
||||
// 插入数据
|
||||
$planId = Db::name('customer_acquisition_task')->insertGetId($data);
|
||||
|
||||
if (!$planId) {
|
||||
throw new \Exception('添加计划失败');
|
||||
}
|
||||
|
||||
|
||||
//订单
|
||||
if ($params['sceneId'] == 2) {
|
||||
if (!empty($params['orderFileUrl'])) {
|
||||
// 先下载到本地临时文件,再分析,最后删除
|
||||
$originPath = $params['orderFileUrl'];
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'order_');
|
||||
// 判断是否为远程文件
|
||||
if (preg_match('/^https?:\/\//i', $originPath)) {
|
||||
// 远程URL,下载到本地
|
||||
$fileContent = file_get_contents($originPath);
|
||||
if ($fileContent === false) {
|
||||
exit('远程文件下载失败: ' . $originPath);
|
||||
}
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
} else {
|
||||
// 本地文件,直接copy
|
||||
if (!file_exists($originPath)) {
|
||||
exit('文件不存在: ' . $originPath);
|
||||
}
|
||||
copy($originPath, $tmpFile);
|
||||
}
|
||||
// 解析临时文件
|
||||
$ext = strtolower(pathinfo($originPath, PATHINFO_EXTENSION));
|
||||
$rows = [];
|
||||
if (in_array($ext, ['xls', 'xlsx'])) {
|
||||
// 直接用composer自动加载的PHPExcel
|
||||
$excel = \PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) > 1) {
|
||||
array_shift($data); // 去掉表头
|
||||
}
|
||||
|
||||
foreach ($data as $cols) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
} elseif ($ext === 'csv') {
|
||||
$content = file_get_contents($tmpFile);
|
||||
$lines = preg_split('/\r\n|\r|\n/', $content);
|
||||
if (count($lines) > 1) {
|
||||
array_shift($lines); // 去掉表头
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$cols = str_getcsv($line);
|
||||
if (count($cols) >= 6) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unlink($tmpFile);
|
||||
exit('暂不支持的文件类型: ' . $ext);
|
||||
}
|
||||
// 删除临时文件
|
||||
unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
//电话获客
|
||||
if ($params['sceneId'] == 5) {
|
||||
$rows = Db::name('call_recording')
|
||||
->where('companyId', $this->getUserInfo('companyId'))
|
||||
->group('phone')
|
||||
->field('id,phone')
|
||||
->select();
|
||||
}
|
||||
|
||||
|
||||
//群获客
|
||||
if ($params['sceneId'] == 7) {
|
||||
if (!empty($params['wechatGroups']) && is_array($params['wechatGroups'])) {
|
||||
$rows = Db::name('wechat_group_member')->alias('gm')
|
||||
->join('wechat_account wa', 'gm.identifier = wa.wechatId')
|
||||
->whereIn('gm.groupId', $params['wechatGroups'])
|
||||
->group('gm.identifier')
|
||||
->column('wa.id,wa.wechatId,wa.alias,wa.phone');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (in_array($params['sceneId'], [2, 5, 7]) && !empty($rows) && is_array($rows)) {
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($rows);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($rows, $i, $batchSize);
|
||||
|
||||
if (!empty($batchRows)) {
|
||||
// 1. 提取当前批次的phone
|
||||
$phones = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone)) {
|
||||
$phones[] = $phone;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 批量查询已存在的phone
|
||||
$existingPhones = [];
|
||||
if (!empty($phones)) {
|
||||
$existing = Db::name('task_customer')
|
||||
->where('task_id', $planId)
|
||||
->where('phone', 'in', $phones)
|
||||
->field('phone')
|
||||
->select();
|
||||
$existingPhones = array_column($existing, 'phone');
|
||||
}
|
||||
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone) && !in_array($phone, $existingPhones)) {
|
||||
$newData[] = [
|
||||
'task_id' => $planId,
|
||||
'name' => '',
|
||||
'source' => '场景获客_' . $params['name'] ?? '',
|
||||
'phone' => $phone,
|
||||
'tags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'siteTags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
Db::name('task_customer')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Db::commit();
|
||||
|
||||
return ResponseHelper::success(['planId' => $planId], '添加计划任务成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JSON格式是否正确
|
||||
*
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
private function validateJson($string)
|
||||
{
|
||||
if (empty($string)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
json_decode($string);
|
||||
return (json_last_error() == JSON_ERROR_NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理分销配置
|
||||
*
|
||||
* @param array $params 请求参数
|
||||
* @param int $companyId 公司ID
|
||||
* @return array 分销配置
|
||||
*/
|
||||
private function processDistributionConfig($params, $companyId)
|
||||
{
|
||||
$distributionEnabled = !empty($params['distributionEnabled']) ? true : false;
|
||||
|
||||
$config = [
|
||||
'enabled' => $distributionEnabled,
|
||||
'channels' => [],
|
||||
'customerRewardAmount' => 0, // 获客奖励金额(分)
|
||||
'addFriendRewardAmount' => 0, // 添加奖励金额(分)
|
||||
];
|
||||
|
||||
// 如果未开启分销,直接返回默认配置
|
||||
if (!$distributionEnabled) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
// 验证渠道ID
|
||||
$channelIds = $params['distributionChannels'] ?? [];
|
||||
if (empty($channelIds) || !is_array($channelIds)) {
|
||||
throw new \Exception('请选择至少一个分销渠道');
|
||||
}
|
||||
|
||||
// 查询有效的渠道(只保留存在且已启用的渠道)
|
||||
$channels = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', 'in', $channelIds],
|
||||
['companyId', '=', $companyId],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->field('id,code,name')
|
||||
->select();
|
||||
|
||||
// 如果没有有效渠道,才报错
|
||||
if (empty($channels)) {
|
||||
throw new \Exception('所选的分销渠道均不存在或已被禁用,请重新选择');
|
||||
}
|
||||
|
||||
// 只保留有效的渠道ID
|
||||
$config['channels'] = array_column($channels, 'id');
|
||||
|
||||
// 验证获客奖励金额(元转分)
|
||||
$customerRewardAmount = isset($params['customerRewardAmount']) ? floatval($params['customerRewardAmount']) : 0;
|
||||
if ($customerRewardAmount < 0) {
|
||||
throw new \Exception('获客奖励金额不能为负数');
|
||||
}
|
||||
if ($customerRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$customerRewardAmount)) {
|
||||
throw new \Exception('获客奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['customerRewardAmount'] = intval(round($customerRewardAmount * 100)); // 元转分
|
||||
|
||||
// 验证添加奖励金额(元转分)
|
||||
$addFriendRewardAmount = isset($params['addFriendRewardAmount']) ? floatval($params['addFriendRewardAmount']) : 0;
|
||||
if ($addFriendRewardAmount < 0) {
|
||||
throw new \Exception('添加奖励金额不能为负数');
|
||||
}
|
||||
if ($addFriendRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$addFriendRewardAmount)) {
|
||||
throw new \Exception('添加奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['addFriendRewardAmount'] = intval(round($addFriendRewardAmount * 100)); // 元转分
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use app\cunkebao\service\DistributionRewardService;
|
||||
|
||||
/**
|
||||
* 对外API接口控制器
|
||||
*/
|
||||
class PostExternalApiV1Controller extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
*
|
||||
* @param array $params 请求参数
|
||||
* @param string $apiKey API密钥
|
||||
* @param string $sign 签名
|
||||
* @return bool
|
||||
*/
|
||||
private function validateSign($params, $apiKey, $sign)
|
||||
{
|
||||
// 1. 从参数中移除sign和apiKey
|
||||
unset($params['sign'], $params['apiKey'],$params['portrait']);
|
||||
|
||||
// 2. 移除空值
|
||||
$params = array_filter($params, function($value) {
|
||||
return !is_null($value) && $value !== '';
|
||||
});
|
||||
|
||||
// 3. 参数按键名升序排序
|
||||
ksort($params);
|
||||
|
||||
// 4. 直接拼接参数值
|
||||
$stringToSign = implode('', array_values($params));
|
||||
|
||||
// 5. 第一次MD5加密
|
||||
$firstMd5 = md5($stringToSign);
|
||||
|
||||
// 6. 拼接apiKey并第二次MD5加密
|
||||
$expectedSign = md5($firstMd5 . $apiKey);
|
||||
|
||||
// 7. 比对签名
|
||||
return $expectedSign === $sign;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外API接口入口
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($params['apiKey'])) {
|
||||
return ResponseHelper::error('apiKey不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['sign'])) {
|
||||
return ResponseHelper::error('sign不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['timestamp'])) {
|
||||
return ResponseHelper::error('timestamp不能为空', 400);
|
||||
}
|
||||
|
||||
// 验证时间戳(允许5分钟误差)
|
||||
if (abs(time() - intval($params['timestamp'])) > 300) {
|
||||
return ResponseHelper::error('请求已过期', 400);
|
||||
}
|
||||
|
||||
// 查询API密钥是否存在
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('apiKey', $params['apiKey'])
|
||||
->where('status', 1)
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('无效的apiKey', 401);
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if (!$this->validateSign($params,$params['apiKey'], $params['sign'])) {
|
||||
return ResponseHelper::error('签名验证失败', 401);
|
||||
}
|
||||
|
||||
$identifier = !empty($params['wechatId']) ? $params['wechatId'] : $params['phone'];
|
||||
|
||||
// 渠道ID(cid),对应 distribution_channel.id
|
||||
$channelId = !empty($params['cid']) ? intval($params['cid']) : 0;
|
||||
|
||||
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $identifier)->find();
|
||||
if (!$trafficPool) {
|
||||
$trafficPoolId =Db::name('traffic_pool')->insertGetId([
|
||||
'identifier' => $identifier,
|
||||
'mobile' => !empty($params['phone']) ? $params['phone'] : '',
|
||||
'createTime' => time()
|
||||
]);
|
||||
}else{
|
||||
$trafficPoolId = $trafficPool['id'];
|
||||
}
|
||||
|
||||
$taskCustomer = Db::name('task_customer')
|
||||
->where('task_id', $plan['id'])
|
||||
->where('phone', $identifier)
|
||||
->find();
|
||||
|
||||
// 处理用户画像
|
||||
if(!empty($params['portrait']) && is_array($params['portrait'])){
|
||||
$this->updatePortrait($params['portrait'],$trafficPoolId,$plan['companyId']);
|
||||
}
|
||||
if (!$taskCustomer) {
|
||||
$tags = !empty($params['tags']) ? explode(',', $params['tags']) : [];
|
||||
$siteTags = !empty($params['siteTags']) ? explode(',', $params['siteTags']) : [];
|
||||
|
||||
// 处理渠道ID:只有在分销配置中允许、且渠道本身正常时,才记录到task_customer
|
||||
$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', '=', $plan['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
if ($channel) {
|
||||
$finalChannelId = intval($channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$customerId = Db::name('task_customer')->insertGetId([
|
||||
'task_id' => $plan['id'],
|
||||
'channelId' => $finalChannelId,
|
||||
'phone' => $identifier,
|
||||
'name' => !empty($params['name']) ? $params['name'] : '',
|
||||
'source' => !empty($params['source']) ? $params['source'] : '',
|
||||
'remark' => !empty($params['remark']) ? $params['remark'] : '',
|
||||
'tags' => json_encode($tags,256),
|
||||
'siteTags' => json_encode($siteTags,256),
|
||||
'createTime' => time(),
|
||||
]);
|
||||
|
||||
// 记录获客奖励(异步处理,不影响主流程)
|
||||
if ($customerId) {
|
||||
try {
|
||||
// 只有在存在有效渠道ID时才触发分佣
|
||||
if ($finalChannelId > 0) {
|
||||
DistributionRewardService::recordCustomerReward($plan['id'], $customerId, $identifier, $finalChannelId);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但不影响主流程
|
||||
\think\facade\Log::error('记录获客奖励失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '新增成功',
|
||||
'data' => $identifier
|
||||
]);
|
||||
}else{
|
||||
$siteTags = !empty($params['siteTags']) ? explode(',',$params['siteTags']) : [];
|
||||
|
||||
// 更新新老标签数据,实现去重
|
||||
$this->updateSiteTags($taskCustomer['id'], $siteTags);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '已存在',
|
||||
'data' => $identifier
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户画像
|
||||
* @param array $data 用户画像数据
|
||||
* @param int $trafficPoolId 流量池id
|
||||
*/
|
||||
public function updatePortrait($data,$trafficPoolId,$companyId)
|
||||
{
|
||||
if(empty($data) || empty($trafficPoolId) || !is_array($data)){
|
||||
return;
|
||||
}
|
||||
|
||||
$type = !empty($data['type']) ? $data['type'] : 0;
|
||||
$source = !empty($data['source']) ? $data['source'] : 0;
|
||||
$sourceData = !empty($data['sourceData']) ? $data['sourceData'] : [];
|
||||
$remark = !empty($data['remark']) ? $data['remark'] : '';
|
||||
$uniqueId = !empty($data['uniqueId']) ? $data['uniqueId'] : 0;
|
||||
ksort($sourceData);
|
||||
$sourceData = json_encode($sourceData,256);
|
||||
|
||||
|
||||
$data = [
|
||||
'companyId' => $companyId,
|
||||
'trafficPoolId' => $trafficPoolId,
|
||||
'type' => $type,
|
||||
'source' => $source,
|
||||
'sourceData' => $sourceData,
|
||||
'remark' => $remark,
|
||||
'uniqueId' => $uniqueId,
|
||||
'count' => 1,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
$res= Db::name('user_portrait')
|
||||
->where(['trafficPoolId'=>$trafficPoolId,'type'=>$type,'source'=>$source,'uniqueId'=>$uniqueId])
|
||||
->where('createTime','>',time()-1800)
|
||||
->find();
|
||||
if($res){
|
||||
$count = $res['count'] + 1;
|
||||
Db::name('user_portrait')->where(['id'=>$res['id']])->update(['count'=>$count,'updateTime'=>time()]);
|
||||
}else{
|
||||
Db::name('user_portrait')->insert($data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新站点标签数据,实现去重
|
||||
* @param int $taskCustomerId 任务客户ID
|
||||
* @param array $newSiteTags 新的站点标签数组
|
||||
*/
|
||||
private function updateSiteTags($taskCustomerId, $newSiteTags)
|
||||
{
|
||||
|
||||
if (empty($taskCustomerId) || empty($newSiteTags) || !is_array($newSiteTags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 获取当前任务客户的站点标签
|
||||
$taskCustomer = Db::name('task_customer')->where('id', $taskCustomerId)->find();
|
||||
|
||||
if (!$taskCustomer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析现有的站点标签
|
||||
$existingSiteTags = [];
|
||||
if (!empty($taskCustomer['siteTags'])) {
|
||||
$existingSiteTags = json_decode($taskCustomer['siteTags'], true);
|
||||
if (!is_array($existingSiteTags)) {
|
||||
$existingSiteTags = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 合并新老标签并去重
|
||||
$mergedSiteTags = array_merge($existingSiteTags, $newSiteTags);
|
||||
$uniqueSiteTags = array_unique($mergedSiteTags);
|
||||
|
||||
// 过滤空值并重新索引数组
|
||||
$uniqueSiteTags = array_values(array_filter($uniqueSiteTags, function($tag) {
|
||||
return !empty(trim($tag));
|
||||
}));
|
||||
|
||||
|
||||
// 更新数据库中的站点标签
|
||||
Db::name('task_customer')->where('id', $taskCustomerId)->update([
|
||||
'siteTags' => json_encode($uniqueSiteTags, JSON_UNESCAPED_UNICODE),
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志,但不影响主流程
|
||||
\think\facade\Log::error('更新站点标签失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use app\cunkebao\controller\BaseController;
|
||||
use library\ResponseHelper;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 更新获客计划控制器
|
||||
*/
|
||||
class PostUpdateAddFriendPlanV1Controller extends BaseController
|
||||
{
|
||||
/**
|
||||
* 更新计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
$params = $this->request->param();
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($params['planId'])) {
|
||||
return ResponseHelper::error('计划ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['sceneId'])) {
|
||||
return ResponseHelper::error('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($params['deviceGroups'])) {
|
||||
return ResponseHelper::error('请选择设备', 400);
|
||||
}
|
||||
|
||||
// 检查计划是否存在
|
||||
$plan = Db::name('customer_acquisition_task')
|
||||
->where('id', $params['planId'])
|
||||
->find();
|
||||
|
||||
if (!$plan) {
|
||||
return ResponseHelper::error('计划不存在', 404);
|
||||
}
|
||||
|
||||
$companyId = $this->getUserInfo('companyId');
|
||||
|
||||
// 处理分销配置
|
||||
$distributionConfig = $this->processDistributionConfig($params, $companyId);
|
||||
|
||||
// 归类参数
|
||||
$msgConf = isset($params['messagePlans']) ? $params['messagePlans'] : [];
|
||||
$tagConf = [
|
||||
'scenarioTags' => $params['scenarioTags'] ?? [],
|
||||
'customTags' => $params['customTags'] ?? [],
|
||||
];
|
||||
$reqConf = [
|
||||
'device' => $params['deviceGroups'] ?? [],
|
||||
'remarkType' => $params['remarkType'] ?? '',
|
||||
'greeting' => $params['greeting'] ?? '',
|
||||
'addFriendInterval' => $params['addFriendInterval'] ?? '',
|
||||
'startTime' => $params['startTime'] ?? '',
|
||||
'endTime' => $params['endTime'] ?? '',
|
||||
];
|
||||
|
||||
// 其余参数归为sceneConf
|
||||
$sceneConf = $params;
|
||||
unset(
|
||||
$sceneConf['id'],
|
||||
$sceneConf['apiKey'],
|
||||
$sceneConf['userId'],
|
||||
$sceneConf['status'],
|
||||
$sceneConf['planId'],
|
||||
$sceneConf['name'],
|
||||
$sceneConf['sceneId'],
|
||||
$sceneConf['messagePlans'],
|
||||
$sceneConf['scenarioTags'],
|
||||
$sceneConf['customTags'],
|
||||
$sceneConf['deviceGroups'],
|
||||
$sceneConf['orderTableFileName'],
|
||||
$sceneConf['userInfo'],
|
||||
$sceneConf['textUrl'],
|
||||
$sceneConf['remarkType'],
|
||||
$sceneConf['greeting'],
|
||||
$sceneConf['addFriendInterval'],
|
||||
$sceneConf['startTime'],
|
||||
$sceneConf['orderTableFile'],
|
||||
$sceneConf['endTime'],
|
||||
$sceneConf['distributionEnabled'],
|
||||
$sceneConf['distributionChannels'],
|
||||
$sceneConf['customerRewardAmount'],
|
||||
$sceneConf['addFriendRewardAmount']
|
||||
);
|
||||
|
||||
// 将分销配置添加到sceneConf中
|
||||
$sceneConf['distribution'] = $distributionConfig;
|
||||
|
||||
// 构建更新数据
|
||||
$data = [
|
||||
'name' => $params['name'],
|
||||
'sceneId' => $params['sceneId'],
|
||||
'sceneConf' => json_encode($sceneConf, JSON_UNESCAPED_UNICODE),
|
||||
'reqConf' => json_encode($reqConf, JSON_UNESCAPED_UNICODE),
|
||||
'msgConf' => json_encode($msgConf, JSON_UNESCAPED_UNICODE),
|
||||
'tagConf' => json_encode($tagConf, JSON_UNESCAPED_UNICODE),
|
||||
'status' => !empty($params['status']) ? 1 : 0,
|
||||
'updateTime' => time(),
|
||||
];
|
||||
|
||||
|
||||
try {
|
||||
// 更新数据
|
||||
$result = Db::name('customer_acquisition_task')
|
||||
->where('id', $params['planId'])
|
||||
->update($data);
|
||||
|
||||
if ($result === false) {
|
||||
throw new \Exception('更新计划失败');
|
||||
}
|
||||
|
||||
//订单
|
||||
if ($params['sceneId'] == 2) {
|
||||
if (!empty($params['orderFileUrl'])) {
|
||||
// 先下载到本地临时文件,再分析,最后删除
|
||||
$originPath = $params['orderFileUrl'];
|
||||
$tmpFile = tempnam(sys_get_temp_dir(), 'order_');
|
||||
// 判断是否为远程文件
|
||||
if (preg_match('/^https?:\/\//i', $originPath)) {
|
||||
// 远程URL,下载到本地
|
||||
$fileContent = file_get_contents($originPath);
|
||||
if ($fileContent === false) {
|
||||
exit('远程文件下载失败: ' . $originPath);
|
||||
}
|
||||
file_put_contents($tmpFile, $fileContent);
|
||||
} else {
|
||||
// 本地文件,直接copy
|
||||
if (!file_exists($originPath)) {
|
||||
exit('文件不存在: ' . $originPath);
|
||||
}
|
||||
copy($originPath, $tmpFile);
|
||||
}
|
||||
// 解析临时文件
|
||||
$ext = strtolower(pathinfo($originPath, PATHINFO_EXTENSION));
|
||||
$rows = [];
|
||||
if (in_array($ext, ['xls', 'xlsx'])) {
|
||||
// 直接用composer自动加载的PHPExcel
|
||||
$excel = \PHPExcel_IOFactory::load($tmpFile);
|
||||
$sheet = $excel->getActiveSheet();
|
||||
$data = $sheet->toArray();
|
||||
if (count($data) > 1) {
|
||||
array_shift($data); // 去掉表头
|
||||
}
|
||||
|
||||
foreach ($data as $cols) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
} elseif ($ext === 'csv') {
|
||||
$content = file_get_contents($tmpFile);
|
||||
$lines = preg_split('/\r\n|\r|\n/', $content);
|
||||
if (count($lines) > 1) {
|
||||
array_shift($lines); // 去掉表头
|
||||
foreach ($lines as $line) {
|
||||
if (trim($line) === '') continue;
|
||||
$cols = str_getcsv($line);
|
||||
if (count($cols) >= 6) {
|
||||
$rows[] = [
|
||||
'name' => isset($cols[0]) ? trim($cols[0]) : '',
|
||||
'phone' => isset($cols[1]) ? trim($cols[1]) : '',
|
||||
'wechatId' => isset($cols[2]) ? trim($cols[2]) : '',
|
||||
'source' => isset($cols[3]) ? trim($cols[3]) : '',
|
||||
'orderAmount' => isset($cols[4]) ? trim($cols[4]) : '',
|
||||
'orderDate' => isset($cols[5]) ? trim($cols[5]) : '',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unlink($tmpFile);
|
||||
exit('暂不支持的文件类型: ' . $ext);
|
||||
}
|
||||
// 删除临时文件
|
||||
unlink($tmpFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//电话获客
|
||||
if ($params['sceneId'] == 5) {
|
||||
$rows = Db::name('call_recording')
|
||||
->where('companyId', $this->getUserInfo('companyId'))
|
||||
->group('phone')
|
||||
->field('id,phone')
|
||||
->select();
|
||||
}
|
||||
|
||||
//群获客
|
||||
if ($params['sceneId'] == 7) {
|
||||
if (!empty($params['wechatGroups']) && is_array($params['wechatGroups'])) {
|
||||
$rows = Db::name('wechat_group_member')->alias('gm')
|
||||
->join('wechat_account wa', 'gm.identifier = wa.wechatId')
|
||||
->whereIn('gm.groupId', $params['wechatGroups'])
|
||||
->group('gm.identifier')
|
||||
->column('wa.id,wa.wechatId,wa.alias,wa.phone');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (in_array($params['sceneId'], [2, 5, 7]) && !empty($rows) && is_array($rows)) {
|
||||
// 1000条为一组进行批量处理
|
||||
$batchSize = 1000;
|
||||
$totalRows = count($rows);
|
||||
|
||||
for ($i = 0; $i < $totalRows; $i += $batchSize) {
|
||||
$batchRows = array_slice($rows, $i, $batchSize);
|
||||
if (!empty($batchRows)) {
|
||||
// 1. 提取当前批次的phone
|
||||
// 1. 提取当前批次的phone
|
||||
$phones = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone)) {
|
||||
$phones[] = $phone;
|
||||
}
|
||||
}
|
||||
// 2. 批量查询已存在的phone
|
||||
$existingPhones = [];
|
||||
if (!empty($phones)) {
|
||||
$existing = Db::name('task_customer')
|
||||
->where('task_id', $params['planId'])
|
||||
->where('phone', 'in', $phones)
|
||||
->field('phone')
|
||||
->select();
|
||||
$existingPhones = array_column($existing, 'phone');
|
||||
}
|
||||
|
||||
// 3. 过滤出新数据,批量插入
|
||||
$newData = [];
|
||||
foreach ($batchRows as $row) {
|
||||
if (!empty($row['phone'])) {
|
||||
$phone = $row['phone'];
|
||||
} elseif (!empty($row['alias'])) {
|
||||
$phone = $row['alias'];
|
||||
} else {
|
||||
$phone = $row['wechatId'];
|
||||
}
|
||||
if (!empty($phone) && !in_array($phone, $existingPhones)) {
|
||||
$newData[] = [
|
||||
'task_id' => $params['planId'],
|
||||
'name' => !empty($row['name']) ? $row['name'] : '',
|
||||
'source' => '场景获客_' . $params['name'] ?? '',
|
||||
'phone' => $phone,
|
||||
'tags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'siteTags' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'createTime' => time(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 批量插入新数据
|
||||
if (!empty($newData)) {
|
||||
Db::name('task_customer')->insertAll($newData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ResponseHelper::success(['planId' => $params['planId']], '更新计划任务成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 回滚事务
|
||||
Db::rollback();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理分销配置
|
||||
*
|
||||
* @param array $params 请求参数
|
||||
* @param int $companyId 公司ID
|
||||
* @return array 分销配置
|
||||
*/
|
||||
private function processDistributionConfig($params, $companyId)
|
||||
{
|
||||
$distributionEnabled = !empty($params['distributionEnabled']) ? true : false;
|
||||
|
||||
$config = [
|
||||
'enabled' => $distributionEnabled,
|
||||
'channels' => [],
|
||||
'customerRewardAmount' => 0, // 获客奖励金额(分)
|
||||
'addFriendRewardAmount' => 0, // 添加奖励金额(分)
|
||||
];
|
||||
|
||||
// 如果未开启分销,直接返回默认配置
|
||||
if (!$distributionEnabled) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
// 验证渠道ID
|
||||
$channelIds = $params['distributionChannels'] ?? [];
|
||||
if (empty($channelIds) || !is_array($channelIds)) {
|
||||
throw new \Exception('请选择至少一个分销渠道');
|
||||
}
|
||||
|
||||
// 查询有效的渠道(只保留存在且已启用的渠道)
|
||||
$channels = Db::name('distribution_channel')
|
||||
->where([
|
||||
['id', 'in', $channelIds],
|
||||
['companyId', '=', $companyId],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->field('id,code,name')
|
||||
->select();
|
||||
|
||||
// 如果没有有效渠道,才报错
|
||||
if (empty($channels)) {
|
||||
throw new \Exception('所选的分销渠道均不存在或已被禁用,请重新选择');
|
||||
}
|
||||
|
||||
// 只保留有效的渠道ID
|
||||
$config['channels'] = array_column($channels, 'id');
|
||||
|
||||
// 验证获客奖励金额(元转分)
|
||||
$customerRewardAmount = isset($params['customerRewardAmount']) ? floatval($params['customerRewardAmount']) : 0;
|
||||
if ($customerRewardAmount < 0) {
|
||||
throw new \Exception('获客奖励金额不能为负数');
|
||||
}
|
||||
if ($customerRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$customerRewardAmount)) {
|
||||
throw new \Exception('获客奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['customerRewardAmount'] = intval(round($customerRewardAmount * 100)); // 元转分
|
||||
|
||||
// 验证添加奖励金额(元转分)
|
||||
$addFriendRewardAmount = isset($params['addFriendRewardAmount']) ? floatval($params['addFriendRewardAmount']) : 0;
|
||||
if ($addFriendRewardAmount < 0) {
|
||||
throw new \Exception('添加奖励金额不能为负数');
|
||||
}
|
||||
if ($addFriendRewardAmount > 0 && !preg_match('/^\d+(\.\d{1,2})?$/', (string)$addFriendRewardAmount)) {
|
||||
throw new \Exception('添加奖励金额格式不正确,最多保留2位小数');
|
||||
}
|
||||
$config['addFriendRewardAmount'] = intval(round($addFriendRewardAmount * 100)); // 元转分
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
403
application/cunkebao/controller/plan/PosterWeChatMiniProgram.php
Normal file
403
application/cunkebao/controller/plan/PosterWeChatMiniProgram.php
Normal file
@@ -0,0 +1,403 @@
|
||||
<?php
|
||||
|
||||
namespace app\cunkebao\controller\plan;
|
||||
|
||||
use think\Controller;
|
||||
use think\Request;
|
||||
use EasyWeChat\Factory;
|
||||
use think\facade\Env;
|
||||
|
||||
// use EasyWeChat\Kernel\Exceptions\DecryptException;
|
||||
use EasyWeChat\Kernel\Http\StreamResponse;
|
||||
use think\Db;
|
||||
|
||||
class PosterWeChatMiniProgram extends Controller
|
||||
{
|
||||
|
||||
protected $config;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// 从环境变量获取配置
|
||||
$this->config = [
|
||||
'app_id' => Env::get('weChat.appidMiniApp','wx789850448e26c91d'),
|
||||
'secret' => Env::get('weChat.secretMiniApp','d18f75b3a3623cb40da05648b08365a1'),
|
||||
'response_type' => 'array'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
return 'Hello, World!';
|
||||
}
|
||||
|
||||
|
||||
// 生成小程序码,存客宝-操盘手调用
|
||||
public function generateMiniProgramCodeWithScene($taskId = '', $channelId = 0)
|
||||
{
|
||||
|
||||
if (empty($taskId)) {
|
||||
return json_encode(['code' => 500, 'data' => '', 'msg' => '任务id不能为空']);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$app = Factory::miniProgram($this->config);
|
||||
// scene参数长度限制为32位
|
||||
// 如果提供了channelId,格式为:taskId,channelId
|
||||
// 如果没有channelId,格式为:taskId
|
||||
if (!empty($channelId) && $channelId > 0) {
|
||||
$scene = sprintf("%s,%s", $taskId, $channelId);
|
||||
} else {
|
||||
$scene = sprintf("%s", $taskId);
|
||||
}
|
||||
|
||||
// 确保scene长度不超过32位
|
||||
if (strlen($scene) > 32) {
|
||||
$scene = substr($scene, 0, 32);
|
||||
}
|
||||
|
||||
// 调用接口生成小程序码
|
||||
$response = $app->app_code->getUnlimit($scene, [
|
||||
'page' => 'pages/poster/index2', // 必须是已经发布的小程序页面
|
||||
'width' => 430, // 二维码的宽度,默认430
|
||||
// 'auto_color' => false, // 自动配置线条颜色
|
||||
// 'line_color' => ['r' => 0, 'g' => 0, 'b' => 0], // 颜色设置
|
||||
// 'is_hyaline' => false, // 是否需要透明底色
|
||||
]);
|
||||
// 保存小程序码到文件
|
||||
if ($response instanceof StreamResponse) {
|
||||
// $filename = 'minicode_' . $taskId . '.png';
|
||||
// $response->saveAs('path/to/codes', $filename);
|
||||
// return 'path/to/codes/' . $filename;
|
||||
|
||||
$img = $response->getBody()->getContents();//获取图片二进制流
|
||||
$img_base64 = 'data:image/png;base64,' . base64_encode($img);//转化base64
|
||||
return json_encode(['code' => 200, 'data' => $img_base64]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return json_encode(['code' => 500, 'data' => '', 'msg' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// getPhoneNumber
|
||||
public function getPhoneNumber()
|
||||
{
|
||||
|
||||
$taskId = request()->param('id');
|
||||
$code = request()->param('code');
|
||||
// code 不能为空
|
||||
if (!$code) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => 'code不能为空'
|
||||
]);
|
||||
}
|
||||
|
||||
$task = Db::name('customer_acquisition_task')->where('id', $taskId)->find();
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
$app = Factory::miniProgram($this->config);
|
||||
|
||||
$result = $app->phone_number->getUserPhoneNumber($code);
|
||||
|
||||
if ($result['errcode'] == 0 && isset($result['phone_info']['phoneNumber'])) {
|
||||
|
||||
// TODO 拿到手机号之后的后续操作:
|
||||
// 1. 先写入 ck_traffic_pool 表 identifier mobile 都是 用 phone字段的值
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $result['phone_info']['phoneNumber'])->find();
|
||||
if (!$trafficPool) {
|
||||
Db::name('traffic_pool')->insert([
|
||||
'identifier' => $result['phone_info']['phoneNumber'],
|
||||
'mobile' => $result['phone_info']['phoneNumber'],
|
||||
'createTime' => time()
|
||||
]);
|
||||
}
|
||||
// 2. 写入 ck_task_customer: 以 task_id ~~identifier~~ phone 为条件,如果存在则忽略,使用类似laravel的firstOrcreate(但我不知道thinkphp5.1里的写法)
|
||||
// $taskCustomer = Db::name('task_customer')->where('task_id', $taskId)->where('identifier', $result['phone_info']['phoneNumber'])->find();
|
||||
$taskCustomer = Db::name('task_customer')
|
||||
->where('task_id', $taskId)
|
||||
->where('phone', $result['phone_info']['phoneNumber'])
|
||||
->find();
|
||||
if (!$taskCustomer) {
|
||||
// 渠道ID(cid),对应 distribution_channel.id
|
||||
$channelId = intval($this->request->param('cid', 0));
|
||||
|
||||
$finalChannelId = 0;
|
||||
if ($channelId > 0) {
|
||||
// 获取任务信息,解析分销配置
|
||||
$sceneConf = json_decode($task['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', '=', $task['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
if ($channel) {
|
||||
$finalChannelId = $channelId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$customerId = Db::name('task_customer')->insertGetId([
|
||||
'task_id' => $taskId,
|
||||
'channelId' => $finalChannelId,
|
||||
// 'identifier' => $result['phone_info']['phoneNumber'],
|
||||
'phone' => $result['phone_info']['phoneNumber'],
|
||||
'source' => $task['name'],
|
||||
'createTime' => time(),
|
||||
'tags' => json_encode([]),
|
||||
'siteTags' => json_encode([]),
|
||||
]);
|
||||
|
||||
// 记录获客奖励(异步处理,不影响主流程)
|
||||
if ($customerId) {
|
||||
try {
|
||||
if ($finalChannelId > 0) {
|
||||
\app\cunkebao\service\DistributionRewardService::recordCustomerReward(
|
||||
$taskId,
|
||||
$customerId,
|
||||
$result['phone_info']['phoneNumber'],
|
||||
$finalChannelId
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但不影响主流程
|
||||
\think\facade\Log::error('记录获客奖励失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// return $result['phone_info']['phoneNumber'];
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '获取手机号成功',
|
||||
'data' => $result['phone_info']['phoneNumber']
|
||||
]);
|
||||
} else {
|
||||
// return null;
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '获取手机号失败: ' . $result['errmsg'] ?? '未知错误'
|
||||
]);
|
||||
}
|
||||
|
||||
// return $result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function decryptphones()
|
||||
{
|
||||
|
||||
$taskId = request()->param('id');
|
||||
$rawInput = trim((string)request()->param('phone', ''));
|
||||
// 渠道ID(cid),对应 distribution_channel.id
|
||||
$channelId = intval(request()->param('cid', 0));
|
||||
if ($rawInput === '') {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '手机号或微信号不能为空'
|
||||
]);
|
||||
}
|
||||
$task = Db::name('customer_acquisition_task')->where('id', $taskId)->find();
|
||||
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
// 预先根据任务的分销配置校验渠道是否有效(仅当传入了cid时)
|
||||
$finalChannelId = 0;
|
||||
if ($channelId > 0) {
|
||||
$sceneConf = json_decode($task['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', '=', $task['companyId']],
|
||||
['status', '=', 'enabled'],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
if ($channel) {
|
||||
$finalChannelId = $channelId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$lines = preg_split('/\r\n|\r|\n/', $rawInput);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
$parts = array_map('trim', explode(',', $line, 2));
|
||||
$identifier = $parts[0] ?? '';
|
||||
$remark = $parts[1] ?? '';
|
||||
if ($identifier === '') {
|
||||
continue;
|
||||
}
|
||||
$isPhone = preg_match('/^\+?\d{6,}$/', $identifier);
|
||||
$trafficPool = Db::name('traffic_pool')->where('identifier', $identifier)->find();
|
||||
if (!$trafficPool) {
|
||||
$insertData = [
|
||||
'identifier' => $identifier,
|
||||
'createTime' => time()
|
||||
];
|
||||
if ($isPhone) {
|
||||
$insertData['mobile'] = $identifier;
|
||||
} else {
|
||||
$insertData['wechatId'] = $identifier;
|
||||
}
|
||||
Db::name('traffic_pool')->insert($insertData);
|
||||
} else {
|
||||
$updates = [];
|
||||
if ($isPhone && empty($trafficPool['mobile'])) {
|
||||
$updates['mobile'] = $identifier;
|
||||
}
|
||||
if (!$isPhone && empty($trafficPool['wechatId'])) {
|
||||
$updates['wechatId'] = $identifier;
|
||||
}
|
||||
if (!empty($updates)) {
|
||||
$updates['updateTime'] = time();
|
||||
Db::name('traffic_pool')->where('id', $trafficPool['id'])->update($updates);
|
||||
}
|
||||
}
|
||||
|
||||
$taskCustomer = Db::name('task_customer')
|
||||
->where('task_id', $taskId)
|
||||
->where('phone', $identifier)
|
||||
->find();
|
||||
if (empty($taskCustomer)) {
|
||||
$insertCustomer = [
|
||||
'task_id' => $taskId,
|
||||
'channelId' => $finalChannelId, // 记录本次导入归属的分销渠道(如有)
|
||||
'phone' => $identifier,
|
||||
'source' => $task['name'],
|
||||
'createTime'=> time(),
|
||||
'tags' => json_encode([]),
|
||||
'siteTags' => json_encode([]),
|
||||
];
|
||||
if ($remark !== '') {
|
||||
$insertCustomer['remark'] = $remark;
|
||||
}
|
||||
// 使用 insertGetId 以便在需要时记录获客奖励
|
||||
$customerId = Db::name('task_customer')->insertGetId($insertCustomer);
|
||||
|
||||
// 表单录入成功即视为一次获客:
|
||||
// 仅在存在有效渠道ID时,记录获客奖励(谁的cid谁获客)
|
||||
if (!empty($customerId) && $finalChannelId > 0) {
|
||||
try {
|
||||
\app\cunkebao\service\DistributionRewardService::recordCustomerReward(
|
||||
$taskId,
|
||||
$customerId,
|
||||
$identifier,
|
||||
$finalChannelId
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误但不影响主流程
|
||||
\think\facade\Log::error('记录获客奖励失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
} elseif ($remark !== '' && $taskCustomer['remark'] !== $remark) {
|
||||
Db::name('task_customer')
|
||||
->where('id', $taskCustomer['id'])
|
||||
->update([
|
||||
'remark' => $remark,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return $phone;
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '操作成功',
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
// return $result;
|
||||
|
||||
|
||||
// todo 获取海报获客任务的任务/海报数据 -- 表还没设计好,不急 ck_customer_acquisition_task
|
||||
public
|
||||
function getPosterTaskData()
|
||||
{
|
||||
$id = request()->param('id');
|
||||
$task = Db::name('customer_acquisition_task')
|
||||
->where(['id' => $id, 'deleteTime' => 0])
|
||||
->field('id,name,sceneConf,status')
|
||||
->find();
|
||||
if (!$task) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务不存在'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($task['status'] == 0) {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'message' => '任务已结束'
|
||||
]);
|
||||
}
|
||||
|
||||
$sceneConf = json_decode($task['sceneConf'], true);
|
||||
|
||||
if (isset($sceneConf['posters']['url'])) {
|
||||
$posterUrl = !empty($sceneConf['posters']['url']);
|
||||
} else {
|
||||
$posterUrl = 'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/%E7%82%B9%E5%87%BB%E5%92%A8%E8%AF%A2-FTiyAMAPop2g9LvjLOLDz0VwPg3KVu.gif';
|
||||
}
|
||||
|
||||
|
||||
if (isset($sceneConf['tips'])) {
|
||||
$sTip = $sceneConf['tips'];
|
||||
} else {
|
||||
$sTip = '';
|
||||
}
|
||||
|
||||
unset($task['sceneConf']);
|
||||
$task['sTip'] = $sTip;
|
||||
|
||||
$data = [
|
||||
'id' => $task['id'],
|
||||
'name' => $task['name'],
|
||||
'poster' => ['sUrl' => $posterUrl],
|
||||
'task' => $task,
|
||||
];
|
||||
|
||||
|
||||
// todo 只需 返回 poster_url success_tip
|
||||
return json([
|
||||
'code' => 200,
|
||||
'message' => '获取海报获客任务数据成功',
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user