Files
CKB-Interface/application/cunkebao/controller/TrafficPoolV2Controller.php
2026-02-04 11:02:33 +08:00

898 lines
30 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace app\cunkebao\controller;
use app\cunkebao\service\TrafficPoolService;
use app\cunkebao\service\TrafficPoolGroupService;
use app\common\model\TrafficPoolGroup;
use app\common\model\TrafficPoolCompany;
use app\common\model\TrafficPoolTag;
use app\common\model\TrafficPoolTagCategory;
use app\common\model\TrafficPoolTagDefine;
use app\common\model\TrafficPoolAllotRecord;
use app\common\model\TrafficPoolSource;
use app\common\model\TrafficPoolBehavior;
use app\common\service\ClassTableService;
use library\ResponseHelper;
/**
* 流量池控制器 V2
* 基于新架构的流量池 API 接口
*/
class TrafficPoolV2Controller extends BaseController
{
/**
* @var TrafficPoolService
*/
protected $poolService;
/**
* @var TrafficPoolGroupService
*/
protected $groupService;
public function __construct(ClassTableService $classTable)
{
parent::__construct($classTable);
$this->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());
}
}
}