存客宝应用接口初始化

This commit is contained in:
Ghost
2026-01-05 10:25:32 +08:00
parent fa37227954
commit 02797ba50a
414 changed files with 85530 additions and 75 deletions

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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 . '%';
}
}

View 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,'获取成功');
}
}

View File

@@ -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;
}
}

View File

@@ -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'];
// 渠道IDcid对应 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());
}
}
}

View File

@@ -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;
}
}

View 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) {
// 渠道IDcid对应 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', ''));
// 渠道IDcid对应 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
]);
}
}