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_v1 表 identifier mobile 都是 用 phone字段的值 // $trafficPool = Db::name('traffic_pool_v1')->where('identifier', $result['phone_info']['phoneNumber'])->find(); // if (!$trafficPool) { // Db::name('traffic_pool_v1')->insert([ // 'identifier' => $result['phone_info']['phoneNumber'], // 'mobile' => $result['phone_info']['phoneNumber'], // 'createTime' => time() // ]); // } // ========== 旧版流量池代码结束(已迁移到V2实时同步) ========== // 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([]), ]); // 实时同步到 V2 流量池系统(异步处理,不影响主流程) if ($customerId) { try { $poolService = new TrafficPoolService(); $identifier = $result['phone_info']['phoneNumber']; // 准备流量池数据 $poolData = [ 'identifierType' => 2, // 手机号 'mobile' => $identifier, ]; // 准备公司流量数据 $companyData = [ 'phone' => $identifier, ]; // 准备来源数据 $sourceData = [ 'sourceName' => $task['name'] ?? '海报获客', 'extra' => json_encode([ 'planId' => $taskId, 'planName' => $task['name'] ?? '', 'channelId' => $finalChannelId, 'customerId' => $customerId, 'source' => 'poster_miniprogram', ], JSON_UNESCAPED_UNICODE), ]; // 同步到 V2 流量池 $poolService->enterPool( $identifier, $task['companyId'], TrafficPoolSource::SOURCE_TYPE_POSTER, // 海报获客 $poolData, $companyData, $sourceData ); } catch (\Exception $e) { // 记录错误但不影响主流程 \think\facade\Log::error('同步到V2流量池失败:' . $e->getMessage()); } } // 记录获客奖励(异步处理,不影响主流程) 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_v1')->where('identifier', $identifier)->find(); // if (!$trafficPool) { // $insertData = [ // 'identifier' => $identifier, // 'createTime' => time() // ]; // if ($isPhone) { // $insertData['mobile'] = $identifier; // } else { // $insertData['wechatId'] = $identifier; // } // Db::name('traffic_pool_v1')->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_v1')->where('id', $trafficPool['id'])->update($updates); // } // } // ========== 旧版流量池代码结束(已迁移到V2实时同步) ========== $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); // 实时同步到 V2 流量池系统(异步处理,不影响主流程) if (!empty($customerId)) { try { $poolService = new TrafficPoolService(); // 判断 identifier 类型 $identifierType = $isPhone ? 2 : 1; // 2=手机号, 1=微信号 // 准备流量池数据 $poolData = [ 'identifierType' => $identifierType, 'mobile' => $isPhone ? $identifier : '', 'wechatId' => !$isPhone ? $identifier : '', ]; // 准备公司流量数据 $companyData = [ 'phone' => $isPhone ? $identifier : '', 'remark' => $remark, ]; // 准备来源数据 $sourceData = [ 'sourceName' => $task['name'] ?? '海报获客', 'remark' => $remark, 'extra' => json_encode([ 'planId' => $taskId, 'planName' => $task['name'] ?? '', 'channelId' => $finalChannelId, 'customerId' => $customerId, 'source' => 'poster_batch_import', ], JSON_UNESCAPED_UNICODE), ]; // 同步到 V2 流量池 $poolService->enterPool( $identifier, $task['companyId'], TrafficPoolSource::SOURCE_TYPE_POSTER, // 海报获客 $poolData, $companyData, $sourceData ); } catch (\Exception $e) { // 记录错误但不影响主流程 \think\facade\Log::error('同步到V2流量池失败:' . $e->getMessage()); } } // 表单录入成功即视为一次获客: // 仅在存在有效渠道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'); $oldId = request()->param('oldId'); // 兼容旧数据:如果传了 oldId,通过 legacyId 和 isLegacy 查找 if (!empty($oldId)) { $task = Db::name('customer_acquisition_task') ->where([ 'legacyId' => $oldId, 'isLegacy' => 1, 'deleteTime' => 0 ]) ->field('id,name,sceneConf,status') ->find(); } elseif (!empty($id)) { // 新数据:直接用 id 查找 $task = Db::name('customer_acquisition_task') ->where(['id' => $id, 'deleteTime' => 0]) ->field('id,name,sceneConf,status') ->find(); } else { return json([ 'code' => 400, 'message' => '任务ID不能为空' ]); } 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']) && !empty($sceneConf['posters']['url'])) { $posterUrl = $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 ]); } }