poolService = new TrafficPoolService(); $this->groupService = new TrafficPoolGroupService(); } // ==================== 分组相关接口 ==================== /** * 获取流量池分组列表 * @return \think\response\Json */ public function getGroups() { $companyId = $this->getUserInfo('companyId'); try { $groups = $this->groupService->getGroupList($companyId, true); return ResponseHelper::success($groups); } catch (\Exception $e) { return ResponseHelper::error('获取分组列表失败:' . $e->getMessage()); } } /** * 获取分组详情 * @return \think\response\Json */ public function getGroupDetail() { $groupId = $this->request->param('groupId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); if (empty($groupId)) { return ResponseHelper::error('分组ID不能为空'); } try { $detail = $this->groupService->getGroupDetail($groupId, $companyId); if (!$detail) { return ResponseHelper::error('分组不存在'); } return ResponseHelper::success($detail); } catch (\Exception $e) { return ResponseHelper::error('获取分组详情失败:' . $e->getMessage()); } } /** * 创建分组 * @return \think\response\Json */ public function createGroup() { $companyId = $this->getUserInfo('companyId'); $userId = $this->getUserInfo('id'); $data = $this->request->param(); if (empty($data['groupName'])) { return ResponseHelper::error('分组名称不能为空'); } try { $group = $this->groupService->createGroup($companyId, $data, $userId); return ResponseHelper::success([ 'id' => $group->id, 'groupName' => $group->groupName ], '创建成功'); } catch (\Exception $e) { return ResponseHelper::error('创建分组失败:' . $e->getMessage()); } } /** * 根据筛选条件预览用户列表 * GET /v1/traffic/pool/v2/preview-users * * @return \think\response\Json */ public function previewUsers() { $companyId = $this->getUserInfo('companyId'); $ruleConfig = $this->request->param('ruleConfig'); $page = $this->request->param('page', 1, 'intval'); $pageSize = $this->request->param('pageSize', 20, 'intval'); $keyword = $this->request->param('keyword', ''); if (empty($ruleConfig)) { return ResponseHelper::error('筛选条件不能为空'); } // 如果ruleConfig是JSON字符串,解析它 if (is_string($ruleConfig)) { $ruleConfig = json_decode($ruleConfig, true); } // 从ruleConfig中提取keyword(如果前端放在里面的话) if (empty($keyword) && isset($ruleConfig['keyword'])) { $keyword = $ruleConfig['keyword']; unset($ruleConfig['keyword']); } try { $result = $this->groupService->previewGroupMembers($companyId, $ruleConfig, $page, $pageSize, $keyword); return ResponseHelper::success($result); } catch (\Exception $e) { return ResponseHelper::error('获取用户列表失败:' . $e->getMessage()); } } /** * 获取筛选条件可选项(字段元数据) * GET /v1/traffic/pool/v2/filter-fields * * @return \think\response\Json */ public function getFilterFields() { try { $fields = [ [ 'field' => 'lifecycle', 'label' => '客户周期', 'type' => 'select', 'options' => [ ['label' => '新流量', 'value' => 1], ['label' => '成长期', 'value' => 2], ['label' => '成熟期', 'value' => 3], ['label' => '衰退期', 'value' => 4], ['label' => '流失期', 'value' => 5], ] ], [ 'field' => 'intentionLevel', 'label' => '意向等级', 'type' => 'select', 'options' => [ ['label' => '未知', 'value' => 0], ['label' => '低意向', 'value' => 1], ['label' => '中意向', 'value' => 2], ['label' => '高意向', 'value' => 3], ] ], [ 'field' => 'level', 'label' => '客户等级', 'type' => 'select', 'options' => [ ['label' => '普通', 'value' => 0], ['label' => '白银', 'value' => 1], ['label' => '黄金', 'value' => 2], ['label' => '钻石', 'value' => 3], ] ], [ 'field' => 'gender', 'label' => '性别', 'type' => 'select', 'options' => [ ['label' => '未知', 'value' => 0], ['label' => '男', 'value' => 1], ['label' => '女', 'value' => 2], ] ], [ 'field' => 'friendStatus', 'label' => '好友状态', 'type' => 'select', 'options' => [ ['label' => '未添加', 'value' => 0], ['label' => '已申请', 'value' => 1], ['label' => '已通过', 'value' => 2], ['label' => '已拒绝', 'value' => 3], ['label' => '已删除', 'value' => 4], ] ], [ 'field' => 'province', 'label' => '地区', 'type' => 'province', ], [ 'field' => 'totalOrderAmount', 'label' => '总消费金额', 'type' => 'number', ], [ 'field' => 'totalOrderCount', 'label' => '订单数量', 'type' => 'number', ], [ 'field' => 'totalMsgCount', 'label' => '消息数量', 'type' => 'number', ], [ 'field' => 'rfmF', 'label' => 'RFM-F值', 'type' => 'number', ], [ 'field' => 'rfmM', 'label' => 'RFM-M值', 'type' => 'number', ], ]; return ResponseHelper::success($fields); } catch (\Exception $e) { return ResponseHelper::error('获取字段列表失败:' . $e->getMessage()); } } /** * 更新分组 * @return \think\response\Json */ public function updateGroup() { $groupId = $this->request->param('groupId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); if (empty($groupId)) { return ResponseHelper::error('分组ID不能为空'); } $data = $this->request->param(); try { $this->groupService->updateGroup($groupId, $companyId, $data); return ResponseHelper::success(null, '更新成功'); } catch (\Exception $e) { return ResponseHelper::error('更新分组失败:' . $e->getMessage()); } } /** * 删除分组 * @return \think\response\Json */ public function deleteGroup() { $groupId = $this->request->param('groupId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); if (empty($groupId)) { return ResponseHelper::error('分组ID不能为空'); } try { $this->groupService->deleteGroup($groupId, $companyId); return ResponseHelper::success(null, '删除成功'); } catch (\Exception $e) { return ResponseHelper::error('删除分组失败:' . $e->getMessage()); } } // ==================== 流量池成员相关接口 ==================== /** * 获取分组成员列表(用户列表) * @return \think\response\Json */ public function getGroupMembers() { $groupId = $this->request->param('groupId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); $page = $this->request->param('page', 1, 'intval'); $pageSize = $this->request->param('pageSize', 10, 'intval'); $keyword = $this->request->param('keyword', ''); if (empty($groupId)) { return ResponseHelper::error('分组ID不能为空'); } $filters = [ 'keyword' => $keyword ]; try { $result = $this->groupService->getGroupMembers($groupId, $companyId, $page, $pageSize, $filters); return ResponseHelper::success($result); } catch (\Exception $e) { return ResponseHelper::error('获取成员列表失败:' . $e->getMessage()); } } /** * 获取流量池列表(全量,带筛选) * @return \think\response\Json */ public function getPoolList() { $companyId = $this->getUserInfo('companyId'); $page = $this->request->param('page', 1, 'intval'); $pageSize = $this->request->param('pageSize', 10, 'intval'); $filters = [ 'keyword' => $this->request->param('keyword', ''), 'friendStatus' => $this->request->param('friendStatus'), 'level' => $this->request->param('level'), 'lifecycle' => $this->request->param('lifecycle'), 'allocateStatus' => $this->request->param('allocateStatus'), 'ownerWechatId' => $this->request->param('ownerWechatId', ''), 'rfmMMin' => $this->request->param('rfmMMin'), 'rfmMMax' => $this->request->param('rfmMMax'), ]; // 移除空值 $filters = array_filter($filters, function($v) { return $v !== null && $v !== ''; }); try { $result = $this->poolService->getPoolList($companyId, $page, $pageSize, $filters); return ResponseHelper::success($result); } catch (\Exception $e) { return ResponseHelper::error('获取流量池列表失败:' . $e->getMessage()); } } /** * 获取流量详情 * @return \think\response\Json */ public function getPoolDetail() { $poolCompanyId = $this->request->param('id', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); if (empty($poolCompanyId)) { return ResponseHelper::error('流量ID不能为空'); } try { $detail = $this->poolService->getPoolDetail($poolCompanyId, $companyId); if (!$detail) { return ResponseHelper::error('流量不存在'); } return ResponseHelper::success($detail); } catch (\Exception $e) { return ResponseHelper::error('获取流量详情失败:' . $e->getMessage()); } } /** * 更新流量信息 * @return \think\response\Json */ public function updatePool() { $poolCompanyId = $this->request->param('id', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); if (empty($poolCompanyId)) { return ResponseHelper::error('流量ID不能为空'); } $data = $this->request->param(); try { $this->poolService->updatePool($poolCompanyId, $companyId, $data); return ResponseHelper::success(null, '更新成功'); } catch (\Exception $e) { return ResponseHelper::error('更新失败:' . $e->getMessage()); } } /** * 添加成员到分组(手动分组) * @return \think\response\Json */ public function addMembersToGroup() { $groupId = $this->request->param('groupId', 0, 'intval'); $poolCompanyIds = $this->request->param('poolCompanyIds/a', []); $companyId = $this->getUserInfo('companyId'); $userId = $this->getUserInfo('id'); if (empty($groupId)) { return ResponseHelper::error('分组ID不能为空'); } if (empty($poolCompanyIds)) { return ResponseHelper::error('请选择要添加的成员'); } try { $count = $this->groupService->addMembers($groupId, $poolCompanyIds, $companyId, $userId); return ResponseHelper::success(['count' => $count], "成功添加 {$count} 个成员"); } catch (\Exception $e) { return ResponseHelper::error('添加失败:' . $e->getMessage()); } } /** * 从分组移除成员 * @return \think\response\Json */ public function removeMembersFromGroup() { $groupId = $this->request->param('groupId', 0, 'intval'); $poolCompanyIds = $this->request->param('poolCompanyIds/a', []); $companyId = $this->getUserInfo('companyId'); if (empty($groupId)) { return ResponseHelper::error('分组ID不能为空'); } if (empty($poolCompanyIds)) { return ResponseHelper::error('请选择要移除的成员'); } try { $count = $this->groupService->removeMembers($groupId, $poolCompanyIds, $companyId); return ResponseHelper::success(['count' => $count], "成功移除 {$count} 个成员"); } catch (\Exception $e) { return ResponseHelper::error('移除失败:' . $e->getMessage()); } } // ==================== 标签相关接口 ==================== /** * 获取标签类目列表 * @return \think\response\Json */ public function getTagCategories() { $companyId = $this->getUserInfo('companyId'); $tagType = $this->request->param('tagType'); try { $categories = TrafficPoolTagCategory::getCategoryTree($companyId, $tagType); return ResponseHelper::success($categories); } catch (\Exception $e) { return ResponseHelper::error('获取标签类目失败:' . $e->getMessage()); } } /** * 获取标签定义列表 * @return \think\response\Json */ public function getTagDefines() { $companyId = $this->getUserInfo('companyId'); $tagType = $this->request->param('tagType'); $categoryId = $this->request->param('categoryId'); try { $defines = TrafficPoolTagDefine::getTagDefinesByCompany($companyId, $tagType, $categoryId); return ResponseHelper::success($defines); } catch (\Exception $e) { return ResponseHelper::error('获取标签定义失败:' . $e->getMessage()); } } /** * 为流量添加标签 * @return \think\response\Json */ public function addTag() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $tagDefineId = $this->request->param('tagDefineId', 0, 'intval'); $tagValue = $this->request->param('tagValue', ''); $companyId = $this->getUserInfo('companyId'); $userId = $this->getUserInfo('id'); if (empty($poolCompanyId) || empty($tagDefineId)) { return ResponseHelper::error('参数不完整'); } // 验证流量归属 $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return ResponseHelper::error('流量不存在'); } try { $tag = TrafficPoolTag::addTag( $poolCompanyId, $poolCompany->identifier, $companyId, $tagDefineId, TrafficPoolTag::SOURCE_MANUAL, $userId, $tagValue ); if ($tag) { return ResponseHelper::success(['id' => $tag->id], '添加成功'); } else { return ResponseHelper::error('添加失败'); } } catch (\Exception $e) { return ResponseHelper::error('添加标签失败:' . $e->getMessage()); } } /** * 移除流量标签 * @return \think\response\Json */ public function removeTag() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $tagDefineId = $this->request->param('tagDefineId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); if (empty($poolCompanyId) || empty($tagDefineId)) { return ResponseHelper::error('参数不完整'); } // 验证流量归属 $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return ResponseHelper::error('流量不存在'); } try { $result = TrafficPoolTag::removeTag($poolCompanyId, $tagDefineId); if ($result) { return ResponseHelper::success(null, '移除成功'); } else { return ResponseHelper::error('标签不存在'); } } catch (\Exception $e) { return ResponseHelper::error('移除标签失败:' . $e->getMessage()); } } /** * 获取流量的标签 * @return \think\response\Json */ public function getPoolTags() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $tagType = $this->request->param('tagType'); $companyId = $this->getUserInfo('companyId'); if (empty($poolCompanyId)) { return ResponseHelper::error('流量ID不能为空'); } // 验证流量归属 $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return ResponseHelper::error('流量不存在'); } try { $tags = TrafficPoolTag::getTagsByPoolCompany($poolCompanyId, $tagType); return ResponseHelper::success($tags); } catch (\Exception $e) { return ResponseHelper::error('获取标签失败:' . $e->getMessage()); } } /** * 从标签引擎同步用户标签 * @return \think\response\Json */ public function syncTagsFromEngine() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); $operatorId = $this->getUserInfo('id'); if (empty($poolCompanyId)) { return ResponseHelper::error('流量ID不能为空'); } try { $result = $this->poolService->syncTagsFromEngine($poolCompanyId, $companyId, $operatorId); return ResponseHelper::success($result, "同步成功:已同步 {$result['syncedCount']} 个标签"); } catch (\Exception $e) { return ResponseHelper::error('同步标签失败:' . $e->getMessage()); } } // ==================== 分配相关接口 ==================== /** * 分配流量给客服 * @return \think\response\Json */ public function allocatePool() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $toWechatId = $this->request->param('toWechatId', ''); $toAccountId = $this->request->param('toAccountId', 0, 'intval'); $toUserId = $this->request->param('toUserId', 0, 'intval'); $expireDays = $this->request->param('expireDays', 30, 'intval'); $companyId = $this->getUserInfo('companyId'); $operatorId = $this->getUserInfo('id'); if (empty($poolCompanyId) || empty($toWechatId)) { return ResponseHelper::error('参数不完整'); } // 验证流量归属 $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return ResponseHelper::error('流量不存在'); } try { $fromInfo = [ 'fromWechatId' => $poolCompany->ownerWechatId, 'fromAccountId' => $poolCompany->ownerAccountId, 'fromUserId' => $poolCompany->ownerUserId, ]; $record = TrafficPoolAllotRecord::createAllotRecord( $poolCompanyId, $poolCompany->identifier, $companyId, $toWechatId, $toAccountId ?: null, $toUserId ?: null, $expireDays, $operatorId, $fromInfo ); return ResponseHelper::success(['id' => $record->id], '分配成功'); } catch (\Exception $e) { return ResponseHelper::error('分配失败:' . $e->getMessage()); } } /** * 回收流量分配 * @return \think\response\Json */ public function recyclePool() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); $operatorId = $this->getUserInfo('id'); if (empty($poolCompanyId)) { return ResponseHelper::error('流量ID不能为空'); } // 验证流量归属 $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return ResponseHelper::error('流量不存在'); } try { TrafficPoolAllotRecord::recycleAllot($poolCompanyId, $operatorId); return ResponseHelper::success(null, '回收成功'); } catch (\Exception $e) { return ResponseHelper::error('回收失败:' . $e->getMessage()); } } // ==================== 统计相关接口 ==================== /** * 获取流量池统计数据 * @return \think\response\Json */ public function getStatistics() { $companyId = $this->getUserInfo('companyId'); try { $statistics = $this->poolService->getStatistics($companyId); return ResponseHelper::success($statistics); } catch (\Exception $e) { return ResponseHelper::error('获取统计数据失败:' . $e->getMessage()); } } /** * 分页获取流量来源 * @return \think\response\Json */ public function getPoolSources() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); $page = $this->request->param('page', 1, 'intval'); $pageSize = $this->request->param('pageSize', 20, 'intval'); $keyword = $this->request->param('keyword', ''); if (empty($poolCompanyId)) { return ResponseHelper::error('流量ID不能为空'); } // 验证流量归属 $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return ResponseHelper::error('流量不存在'); } try { $result = TrafficPoolSource::getSourcesWithOwnersPaginated($poolCompanyId, $page, $pageSize, $keyword); return ResponseHelper::success($result); } catch (\Exception $e) { return ResponseHelper::error('获取来源列表失败:' . $e->getMessage()); } } /** * 分页获取流量行为轨迹 * @return \think\response\Json */ public function getPoolBehaviors() { $poolCompanyId = $this->request->param('poolCompanyId', 0, 'intval'); $companyId = $this->getUserInfo('companyId'); $page = $this->request->param('page', 1, 'intval'); $pageSize = $this->request->param('pageSize', 20, 'intval'); $keyword = $this->request->param('keyword', ''); $behaviorType = $this->request->param('behaviorType', 0, 'intval'); if (empty($poolCompanyId)) { return ResponseHelper::error('流量ID不能为空'); } // 验证流量归属 $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return ResponseHelper::error('流量不存在'); } try { $result = TrafficPoolBehavior::getUserJourneyPaginated($poolCompanyId, $page, $pageSize, $keyword, $behaviorType); return ResponseHelper::success($result); } catch (\Exception $e) { return ResponseHelper::error('获取行为轨迹失败:' . $e->getMessage()); } } /** * 计算并更新RFM评分 * POST /v1/traffic/pool/v2/calculate-rfm * * @return \think\response\Json */ public function calculateRfm() { $companyId = $this->getUserInfo('companyId'); $identifier = $this->request->post('identifier', null); // 可选,指定用户标识 try { // 实例化RFM控制器(传递ClassTableService) $rfmController = new RFMController($this->classTable); // 获取配置参数(可从请求参数中获取,或使用默认值) $config = [ 'cycle_days' => $this->request->post('cycle_days', 180), 'weight_R' => $this->request->post('weight_R', 0.4), 'weight_F' => $this->request->post('weight_F', 0.3), 'weight_M' => $this->request->post('weight_M', 0.3), 'score_scale' => $this->request->post('score_scale', 5), ]; // 调用RFM计算方法 // 注意:这里不传ownerWechatId,因为V2系统是按companyId区分的 $result = $rfmController->calculateRfmFromTrafficOrder($identifier, null, $config); if ($result['code'] == 200) { return ResponseHelper::success($result['data'], 'RFM计算完成'); } else { return ResponseHelper::error($result['msg']); } } catch (\Exception $e) { return ResponseHelper::error('RFM计算失败:' . $e->getMessage()); } } /** * 批量更新指定分组的RFM评分 * POST /v1/traffic/pool/v2/group/{groupId}/calculate-rfm * * @return \think\response\Json */ public function calculateGroupRfm() { $companyId = $this->getUserInfo('companyId'); $groupId = $this->request->param('groupId'); if (empty($groupId)) { return ResponseHelper::error('分组ID不能为空'); } try { // 获取分组成员 $members = $this->groupService->getGroupMembers($groupId, $companyId, 1, 9999, []); if (empty($members['list'])) { return ResponseHelper::error('分组无成员'); } // 实例化RFM控制器(传递ClassTableService) $rfmController = new RFMController($this->classTable); $successCount = 0; $failCount = 0; // 为每个成员计算RFM foreach ($members['list'] as $member) { $identifier = $member['identifier']; $result = $rfmController->calculateRfmFromTrafficOrder($identifier, null, []); if ($result['code'] == 200) { $successCount++; } else { $failCount++; } } return ResponseHelper::success([ 'total' => count($members['list']), 'success' => $successCount, 'fail' => $failCount ], 'RFM批量计算完成'); } catch (\Exception $e) { return ResponseHelper::error('RFM批量计算失败:' . $e->getMessage()); } } }