510 lines
18 KiB
PHP
510 lines
18 KiB
PHP
<?php
|
||
|
||
namespace app\common\model;
|
||
|
||
use think\Model;
|
||
use think\Db;
|
||
|
||
/**
|
||
* 流量来源表模型类
|
||
* 表名:ck_traffic_pool_source
|
||
* 用途:记录流量的获取渠道和来源路径
|
||
*/
|
||
class TrafficPoolSource extends Model
|
||
{
|
||
// 设置数据表名
|
||
protected $name = 'traffic_pool_source';
|
||
|
||
// 主键
|
||
protected $pk = 'id';
|
||
|
||
// 自动写入时间戳
|
||
protected $autoWriteTimestamp = true;
|
||
protected $createTime = 'createTime';
|
||
protected $updateTime = 'updateTime';
|
||
|
||
// 来源类型常量
|
||
const SOURCE_TYPE_FRIEND_ADD = 1; // 好友添加
|
||
const SOURCE_TYPE_GROUP_MEMBER = 2; // 群成员
|
||
const SOURCE_TYPE_POSTER = 3; // 海报获客
|
||
const SOURCE_TYPE_PHONE = 4; // 电话获客
|
||
const SOURCE_TYPE_ORDER = 5; // 订单获客
|
||
const SOURCE_TYPE_API = 6; // API导入
|
||
const SOURCE_TYPE_MANUAL = 7; // 手动导入
|
||
const SOURCE_TYPE_FISSION = 8; // 裂变活动
|
||
|
||
// 来源类型名称映射
|
||
const SOURCE_TYPE_NAMES = [
|
||
self::SOURCE_TYPE_FRIEND_ADD => '好友添加',
|
||
self::SOURCE_TYPE_GROUP_MEMBER => '群成员',
|
||
self::SOURCE_TYPE_POSTER => '海报获客',
|
||
self::SOURCE_TYPE_PHONE => '电话获客',
|
||
self::SOURCE_TYPE_ORDER => '订单获客',
|
||
self::SOURCE_TYPE_API => 'API导入',
|
||
self::SOURCE_TYPE_MANUAL => '手动导入',
|
||
self::SOURCE_TYPE_FISSION => '裂变活动',
|
||
];
|
||
|
||
/**
|
||
* 关联公司流量详情
|
||
*/
|
||
public function poolCompany()
|
||
{
|
||
return $this->belongsTo(TrafficPoolCompany::class, 'poolCompanyId', 'id');
|
||
}
|
||
|
||
/**
|
||
* 获取额外信息
|
||
* @param string $value
|
||
* @return array
|
||
*/
|
||
public function getExtraAttr($value)
|
||
{
|
||
return $value ? json_decode($value, true) : [];
|
||
}
|
||
|
||
/**
|
||
* 设置额外信息
|
||
* @param array $value
|
||
* @return string
|
||
*/
|
||
public function setExtraAttr($value)
|
||
{
|
||
return $value ? json_encode($value, JSON_UNESCAPED_UNICODE) : null;
|
||
}
|
||
|
||
/**
|
||
* 兼容历史库:s2_wechat_friend 早期可能不存在 headImgUrl 列
|
||
* @param string $wechatId
|
||
* @return string
|
||
*/
|
||
protected static function getFriendHeadImgUrl(string $wechatId): string
|
||
{
|
||
$wechatId = trim($wechatId);
|
||
if ($wechatId === '') {
|
||
return '';
|
||
}
|
||
|
||
static $hasHeadImgUrl = null;
|
||
if ($hasHeadImgUrl === null) {
|
||
try {
|
||
$cols = Db::query("SHOW COLUMNS FROM s2_wechat_friend LIKE 'headImgUrl'");
|
||
$hasHeadImgUrl = !empty($cols);
|
||
} catch (\Throwable $e) {
|
||
$hasHeadImgUrl = false;
|
||
}
|
||
}
|
||
|
||
if (!$hasHeadImgUrl) {
|
||
return '';
|
||
}
|
||
|
||
try {
|
||
$val = Db::table('s2_wechat_friend')
|
||
->where('wechatId', $wechatId)
|
||
->value('headImgUrl');
|
||
return $val ? (string)$val : '';
|
||
} catch (\Throwable $e) {
|
||
// 兜底:避免因字段缺失导致接口直接 1054
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取来源类型名称
|
||
* @return string
|
||
*/
|
||
public function getSourceTypeNameAttr()
|
||
{
|
||
return self::SOURCE_TYPE_NAMES[$this->sourceType] ?? '未知来源';
|
||
}
|
||
|
||
/**
|
||
* 创建来源记录
|
||
* @param int $poolCompanyId
|
||
* @param string $identifier
|
||
* @param int $companyId
|
||
* @param int $sourceType
|
||
* @param array $data
|
||
* @return static
|
||
*/
|
||
public static function createSource(int $poolCompanyId, string $identifier, int $companyId, int $sourceType, array $data = [])
|
||
{
|
||
// 检查是否为首次来源
|
||
$existSource = self::where('poolCompanyId', $poolCompanyId)->find();
|
||
$isFirstSource = $existSource ? 0 : 1;
|
||
|
||
$insertData = array_merge([
|
||
'poolCompanyId' => $poolCompanyId,
|
||
'identifier' => $identifier,
|
||
'companyId' => $companyId,
|
||
'sourceType' => $sourceType,
|
||
'isFirstSource' => $isFirstSource,
|
||
'createTime' => time(),
|
||
], $data);
|
||
|
||
$source = self::create($insertData);
|
||
|
||
// 如果是首次来源,更新公司流量详情表
|
||
if ($isFirstSource) {
|
||
TrafficPoolCompany::where('id', $poolCompanyId)->update([
|
||
'firstSourceType' => $sourceType,
|
||
'firstSourceTime' => time(),
|
||
'updateTime' => time()
|
||
]);
|
||
}
|
||
|
||
return $source;
|
||
}
|
||
|
||
/**
|
||
* 获取流量的所有来源
|
||
* @param int $poolCompanyId
|
||
* @return \think\Collection
|
||
*/
|
||
public static function getSourcesByPoolCompany(int $poolCompanyId)
|
||
{
|
||
return self::where('poolCompanyId', $poolCompanyId)
|
||
->order('createTime DESC')
|
||
->select();
|
||
}
|
||
|
||
/**
|
||
* 获取流量的所有来源(带群归属信息)
|
||
* @param int $poolCompanyId
|
||
* @param int $limit 限制数量,0表示不限制
|
||
* @return array
|
||
*/
|
||
public static function getSourcesWithOwners(int $poolCompanyId, int $limit = 0): array
|
||
{
|
||
$query = self::where('poolCompanyId', $poolCompanyId)
|
||
->order('createTime DESC');
|
||
|
||
if ($limit > 0) {
|
||
$query->limit($limit);
|
||
}
|
||
|
||
$sources = $query->select()->toArray();
|
||
|
||
if (empty($sources)) {
|
||
return [];
|
||
}
|
||
|
||
// 收集所有群ID
|
||
$chatroomIds = [];
|
||
foreach ($sources as $source) {
|
||
if (!empty($source['sourceChatroomId'])) {
|
||
$chatroomIds[] = $source['sourceChatroomId'];
|
||
}
|
||
}
|
||
|
||
// 查询群信息和归属客服
|
||
$chatroomOwners = [];
|
||
if (!empty($chatroomIds)) {
|
||
$chatroomOwners = self::getChatroomOwners($chatroomIds);
|
||
}
|
||
|
||
// 组装数据(按来源类型和关键标识去重)
|
||
$result = [];
|
||
$seenChatroomIds = []; // 用于群成员来源去重
|
||
$seenFriendIds = []; // 用于好友添加来源去重
|
||
|
||
foreach ($sources as $source) {
|
||
// 群成员来源去重:同一个群只保留一条记录
|
||
if ($source['sourceType'] == self::SOURCE_TYPE_GROUP_MEMBER && !empty($source['sourceChatroomId'])) {
|
||
$chatroomId = $source['sourceChatroomId'];
|
||
if (isset($seenChatroomIds[$chatroomId])) {
|
||
continue; // 跳过重复的群
|
||
}
|
||
$seenChatroomIds[$chatroomId] = true;
|
||
}
|
||
|
||
// 好友添加来源去重:同一个好友(按sourceWechatId或sourceName)只保留一条记录
|
||
if ($source['sourceType'] == self::SOURCE_TYPE_FRIEND_ADD) {
|
||
$friendKey = $source['sourceWechatId'] ?: ($source['sourceName'] ?: '');
|
||
if (!empty($friendKey) && isset($seenFriendIds[$friendKey])) {
|
||
continue; // 跳过重复的好友来源
|
||
}
|
||
if (!empty($friendKey)) {
|
||
$seenFriendIds[$friendKey] = true;
|
||
}
|
||
}
|
||
|
||
$sourceData = $source;
|
||
|
||
// 格式化时间(兼容时间戳和日期字符串)
|
||
if (!empty($source['createTime'])) {
|
||
if (is_numeric($source['createTime'])) {
|
||
$sourceData['createTimeFormatted'] = date('Y-m-d H:i:s', (int)$source['createTime']);
|
||
} else {
|
||
$sourceData['createTimeFormatted'] = $source['createTime'];
|
||
}
|
||
} else {
|
||
$sourceData['createTimeFormatted'] = null;
|
||
}
|
||
|
||
// 添加来源类型名称
|
||
$sourceData['sourceTypeName'] = self::SOURCE_TYPE_NAMES[$source['sourceType']] ?? '未知来源';
|
||
|
||
// 如果是群成员来源,添加群归属信息和群ID展示
|
||
if ($source['sourceType'] == self::SOURCE_TYPE_GROUP_MEMBER && !empty($source['sourceChatroomId'])) {
|
||
$chatroomId = $source['sourceChatroomId'];
|
||
$sourceData['chatroomOwners'] = $chatroomOwners[$chatroomId] ?? [];
|
||
$sourceData['chatroomInfo'] = self::getChatroomInfo($chatroomId);
|
||
// 添加群ID用于展示
|
||
$sourceData['displayId'] = $chatroomId;
|
||
} elseif ($source['sourceType'] == self::SOURCE_TYPE_FRIEND_ADD) {
|
||
// 好友添加来源,添加好友微信ID用于展示
|
||
$sourceData['chatroomOwners'] = [];
|
||
$sourceData['chatroomInfo'] = null;
|
||
$sourceData['displayId'] = $source['sourceWechatId'] ?: '';
|
||
|
||
// 尝试获取好友头像
|
||
if (!empty($source['sourceWechatId'])) {
|
||
$avatar = Db::table('ck_traffic_pool')
|
||
->where('wechatId', $source['sourceWechatId'])
|
||
->value('avatar');
|
||
$sourceData['sourceAvatar'] = $avatar ?: self::getFriendHeadImgUrl((string)$source['sourceWechatId']);
|
||
}
|
||
} else {
|
||
$sourceData['chatroomOwners'] = [];
|
||
$sourceData['chatroomInfo'] = null;
|
||
$sourceData['displayId'] = $source['sourceId'] ?: '';
|
||
}
|
||
|
||
$result[] = $sourceData;
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 获取群的归属客服信息(支持多个客服)
|
||
* @param array $chatroomIds 群聊ID数组
|
||
* @return array [chatroomId => [owner1, owner2, ...]]
|
||
*/
|
||
protected static function getChatroomOwners(array $chatroomIds): array
|
||
{
|
||
if (empty($chatroomIds)) {
|
||
return [];
|
||
}
|
||
|
||
// 查询群和归属账号信息
|
||
$chatrooms = Db::table(['s2_wechat_chatroom' => 'wc'])
|
||
->leftJoin(['s2_wechat_account' => 'wa'], 'wa.wechatId = wc.wechatAccountWechatId')
|
||
->whereIn('wc.chatroomId', $chatroomIds)
|
||
->where('wc.isDeleted', 0)
|
||
->field([
|
||
'wc.chatroomId',
|
||
'wc.nickname as chatroomName',
|
||
'wc.chatroomAvatar',
|
||
'wc.wechatAccountWechatId as ownerWechatId',
|
||
'wc.wechatAccountNickname as ownerNickname',
|
||
'wc.wechatAccountAvatar as ownerAvatar',
|
||
'wc.wechatAccountAlias as ownerAlias',
|
||
'wa.id as accountId',
|
||
'wa.nickName as accountNickname',
|
||
])
|
||
->select();
|
||
|
||
// 按 chatroomId 分组,一个群可能有多条记录(多个客服管理)
|
||
$result = [];
|
||
foreach ($chatrooms as $chatroom) {
|
||
$chatroomId = $chatroom['chatroomId'];
|
||
if (!isset($result[$chatroomId])) {
|
||
$result[$chatroomId] = [];
|
||
}
|
||
|
||
// 避免重复添加相同的客服
|
||
$ownerWechatId = $chatroom['ownerWechatId'];
|
||
$exists = false;
|
||
foreach ($result[$chatroomId] as $existing) {
|
||
if ($existing['ownerWechatId'] === $ownerWechatId) {
|
||
$exists = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$exists && !empty($ownerWechatId)) {
|
||
$result[$chatroomId][] = [
|
||
'ownerWechatId' => $ownerWechatId,
|
||
'ownerNickname' => $chatroom['ownerNickname'] ?: $chatroom['accountNickname'] ?: '',
|
||
'ownerAvatar' => $chatroom['ownerAvatar'] ?: '',
|
||
'ownerAlias' => $chatroom['ownerAlias'] ?: '',
|
||
'accountId' => $chatroom['accountId'],
|
||
];
|
||
}
|
||
}
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 获取群信息
|
||
* @param string $chatroomId 群聊ID
|
||
* @return array|null
|
||
*/
|
||
protected static function getChatroomInfo(string $chatroomId): ?array
|
||
{
|
||
$chatroom = Db::table(['s2_wechat_chatroom' => 'wc'])
|
||
->where('wc.chatroomId', $chatroomId)
|
||
->where('wc.isDeleted', 0)
|
||
->field([
|
||
'wc.id',
|
||
'wc.chatroomId',
|
||
'wc.nickname as chatroomName',
|
||
'wc.chatroomAvatar',
|
||
'wc.createTime',
|
||
])
|
||
->find();
|
||
|
||
if (!$chatroom) {
|
||
return null;
|
||
}
|
||
|
||
// 格式化创建时间(兼容时间戳和日期字符串)
|
||
if (!empty($chatroom['createTime'])) {
|
||
if (is_numeric($chatroom['createTime'])) {
|
||
$chatroom['createTimeFormatted'] = date('Y-m-d H:i:s', (int)$chatroom['createTime']);
|
||
} else {
|
||
$chatroom['createTimeFormatted'] = $chatroom['createTime'];
|
||
}
|
||
} else {
|
||
$chatroom['createTimeFormatted'] = null;
|
||
}
|
||
|
||
return $chatroom;
|
||
}
|
||
|
||
/**
|
||
* 分页获取流量的来源(带群归属信息)
|
||
* @param int $poolCompanyId
|
||
* @param int $page 页码
|
||
* @param int $pageSize 每页数量
|
||
* @param string $keyword 搜索关键词(搜索来源名称)
|
||
* @return array ['list' => [], 'total' => 0, 'page' => 1, 'pageSize' => 10]
|
||
*/
|
||
public static function getSourcesWithOwnersPaginated(int $poolCompanyId, int $page = 1, int $pageSize = 20, string $keyword = ''): array
|
||
{
|
||
$query = self::where('poolCompanyId', $poolCompanyId);
|
||
|
||
// 关键词搜索
|
||
if (!empty($keyword)) {
|
||
$query->where('sourceName', 'like', '%' . $keyword . '%');
|
||
}
|
||
|
||
// 统计总数
|
||
$total = $query->count();
|
||
|
||
// 分页查询
|
||
$sources = $query->order('createTime DESC')
|
||
->page($page, $pageSize)
|
||
->select()
|
||
->toArray();
|
||
|
||
if (empty($sources)) {
|
||
return [
|
||
'list' => [],
|
||
'total' => 0,
|
||
'page' => $page,
|
||
'pageSize' => $pageSize
|
||
];
|
||
}
|
||
|
||
// 收集所有群ID
|
||
$chatroomIds = [];
|
||
foreach ($sources as $source) {
|
||
if (!empty($source['sourceChatroomId'])) {
|
||
$chatroomIds[] = $source['sourceChatroomId'];
|
||
}
|
||
}
|
||
|
||
// 查询群信息和归属客服
|
||
$chatroomOwners = [];
|
||
if (!empty($chatroomIds)) {
|
||
$chatroomOwners = self::getChatroomOwners($chatroomIds);
|
||
}
|
||
|
||
// 组装数据(按来源类型和关键标识去重)
|
||
$result = [];
|
||
$seenChatroomIds = []; // 用于群成员来源去重
|
||
$seenFriendIds = []; // 用于好友添加来源去重
|
||
|
||
foreach ($sources as $source) {
|
||
// 群成员来源去重:同一个群只保留一条记录
|
||
if ($source['sourceType'] == self::SOURCE_TYPE_GROUP_MEMBER && !empty($source['sourceChatroomId'])) {
|
||
$chatroomId = $source['sourceChatroomId'];
|
||
if (isset($seenChatroomIds[$chatroomId])) {
|
||
continue; // 跳过重复的群
|
||
}
|
||
$seenChatroomIds[$chatroomId] = true;
|
||
}
|
||
|
||
// 好友添加来源去重:按来源微信ID或来源名称去重
|
||
if ($source['sourceType'] == self::SOURCE_TYPE_FRIEND_ADD) {
|
||
$friendAddKey = $source['sourceWechatId'] ?? ($source['sourceName'] ?? '');
|
||
if (!empty($friendAddKey) && isset($seenFriendIds[$friendAddKey])) {
|
||
continue; // 跳过重复的好友添加
|
||
}
|
||
$seenFriendIds[$friendAddKey] = true;
|
||
}
|
||
|
||
$sourceData = $source;
|
||
|
||
// 设置显示ID
|
||
if ($source['sourceType'] == self::SOURCE_TYPE_GROUP_MEMBER && !empty($source['sourceChatroomId'])) {
|
||
$sourceData['displayId'] = "群ID:" . $source['sourceChatroomId'];
|
||
} elseif ($source['sourceType'] == self::SOURCE_TYPE_FRIEND_ADD) {
|
||
$sourceData['displayId'] = "好友ID:" . ($source['sourceWechatId'] ?? $source['sourceName'] ?? '-');
|
||
} else {
|
||
$sourceData['displayId'] = null;
|
||
}
|
||
|
||
// 格式化时间(兼容时间戳和日期字符串)
|
||
if (!empty($source['createTime'])) {
|
||
if (is_numeric($source['createTime'])) {
|
||
$sourceData['createTimeFormatted'] = date('Y-m-d H:i:s', (int)$source['createTime']);
|
||
} else {
|
||
$sourceData['createTimeFormatted'] = $source['createTime'];
|
||
}
|
||
} else {
|
||
$sourceData['createTimeFormatted'] = null;
|
||
}
|
||
|
||
// 添加来源类型名称
|
||
$sourceData['sourceTypeName'] = self::SOURCE_TYPE_NAMES[$source['sourceType']] ?? '未知来源';
|
||
|
||
// 如果是群成员来源,添加群归属信息
|
||
if ($source['sourceType'] == self::SOURCE_TYPE_GROUP_MEMBER && !empty($source['sourceChatroomId'])) {
|
||
$chatroomId = $source['sourceChatroomId'];
|
||
$sourceData['chatroomOwners'] = $chatroomOwners[$chatroomId] ?? [];
|
||
$sourceData['chatroomInfo'] = self::getChatroomInfo($chatroomId);
|
||
} elseif ($source['sourceType'] == self::SOURCE_TYPE_FRIEND_ADD) {
|
||
$sourceData['chatroomOwners'] = [];
|
||
$sourceData['chatroomInfo'] = null;
|
||
// 尝试获取好友头像
|
||
if (!empty($source['sourceWechatId'])) {
|
||
$avatar = Db::table('ck_traffic_pool')
|
||
->where('wechatId', $source['sourceWechatId'])
|
||
->value('avatar');
|
||
$sourceData['sourceAvatar'] = $avatar ?: self::getFriendHeadImgUrl((string)$source['sourceWechatId']);
|
||
}
|
||
} else {
|
||
$sourceData['chatroomOwners'] = [];
|
||
$sourceData['chatroomInfo'] = null;
|
||
}
|
||
|
||
$result[] = $sourceData;
|
||
}
|
||
|
||
return [
|
||
'list' => $result,
|
||
'total' => $total,
|
||
'page' => $page,
|
||
'pageSize' => $pageSize
|
||
];
|
||
}
|
||
}
|
||
|
||
|