Files
cunkebao_v3/Server/application/cunkebao/controller/workbench/WorkbenchGroupPushController.php
2026-01-06 10:58:29 +08:00

3634 lines
154 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\workbench;
use app\cunkebao\model\Workbench;
use app\cunkebao\model\WorkbenchGroupPush;
use think\Controller;
use think\Db;
/**
* 工作台 - 群消息推送相关功能
*/
class WorkbenchGroupPushController extends Controller
{
/**
* 工作台类型定义
*/
const TYPE_GROUP_PUSH = 3; // 群消息推送
/**
* 获取群发统计数据
* @return \think\response\Json
*/
public function getGroupPushStats()
{
$workbenchId = $this->request->param('workbenchId', 0);
$timeRange = $this->request->param('timeRange', '7'); // 默认最近7天
$contentLibraryIds = $this->request->param('contentLibraryIds', ''); // 话术组筛选
$userId = $this->request->userInfo['id'];
// 如果指定了工作台ID则验证权限
if (!empty($workbenchId)) {
$workbench = Workbench::where([
['id', '=', $workbenchId],
['userId', '=', $userId],
['type', '=', self::TYPE_GROUP_PUSH],
['isDel', '=', 0]
])->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
}
// 计算时间范围
$days = intval($timeRange);
$startTime = strtotime(date('Y-m-d 00:00:00', strtotime("-{$days} days")));
$endTime = time();
// 构建查询条件
$where = [
['wgpi.createTime', '>=', $startTime],
['wgpi.createTime', '<=', $endTime]
];
{
$id = $this->request->param('id', '');
if (empty($id)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
// 定义关联关系
$with = [
'autoLike' => function ($query) {
$query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends,friendMaxLikes,friendTags,enableFriendTags');
},
'momentsSync' => function ($query) {
$query->field('workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries');
},
'trafficConfig' => function ($query) {
$query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account');
},
'groupPush' => function ($query) {
$query->field('workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt');
},
'groupCreate' => function ($query) {
$query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins');
},
'importContact' => function ($query) {
$query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime');
},
];
$where = [
['id', '=', $id],
['companyId', '=', $this->request->userInfo['companyId']],
['isDel', '=', 0]
];
if (empty($this->request->userInfo['isAdmin'])) {
$where[] = ['userId', '=', $this->request->userInfo['id']];
}
$workbench = Workbench::where($where)
->field('id,name,type,status,autoStart,createTime,updateTime,companyId')
->with($with)
->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
// 处理配置信息
switch ($workbench->type) {
//自动点赞
case self::TYPE_AUTO_LIKE:
if (!empty($workbench->autoLike)) {
$workbench->config = $workbench->autoLike;
$workbench->config->deviceGroups = json_decode($workbench->config->devices, true);
$workbench->config->wechatFriends = json_decode($workbench->config->friends, true);
$workbench->config->targetType = 2;
//$workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true);
$workbench->config->contentTypes = json_decode($workbench->config->contentTypes, true);
// 添加今日点赞数
$startTime = strtotime(date('Y-m-d') . ' 00:00:00');
$endTime = strtotime(date('Y-m-d') . ' 23:59:59');
$todayLikeCount = Db::name('workbench_auto_like_item')
->where('workbenchId', $workbench->id)
->whereTime('createTime', 'between', [$startTime, $endTime])
->count();
// 添加总点赞数
$totalLikeCount = Db::name('workbench_auto_like_item')
->where('workbenchId', $workbench->id)
->count();
$workbench->config->todayLikeCount = $todayLikeCount;
$workbench->config->totalLikeCount = $totalLikeCount;
unset($workbench->autoLike, $workbench->auto_like);
}
break;
//自动同步朋友圈
case self::TYPE_MOMENTS_SYNC:
if (!empty($workbench->momentsSync)) {
$workbench->config = $workbench->momentsSync;
$workbench->config->deviceGroups = json_decode($workbench->config->devices, true);
$workbench->config->contentGroups = json_decode($workbench->config->contentLibraries, true);
//同步记录
$sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->count();
$workbench->syncCount = $sendNum;
$lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->value('createTime');
$workbench->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--';
unset($workbench->momentsSync, $workbench->moments_sync);
}
break;
//群推送
case self::TYPE_GROUP_PUSH:
if (!empty($workbench->groupPush)) {
$workbench->config = $workbench->groupPush;
$workbench->config->targetType = isset($workbench->config->targetType) ? intval($workbench->config->targetType) : 1; // 默认1=群推送
$workbench->config->groupPushSubType = isset($workbench->config->groupPushSubType) ? intval($workbench->config->groupPushSubType) : 1; // 默认1=群群发
$workbench->config->ownerWechatIds = json_decode($workbench->config->ownerWechatIds ?? '[]', true) ?: [];
// 根据targetType解析不同的数据
if ($workbench->config->targetType == 1) {
// 群推送
$workbench->config->wechatGroups = json_decode($workbench->config->groups, true) ?: [];
$workbench->config->wechatFriends = [];
$workbench->config->trafficPools = [];
// 群推送不需要devices字段
// 群公告相关字段
if ($workbench->config->groupPushSubType == 2) {
$workbench->config->announcementContent = isset($workbench->config->announcementContent) ? $workbench->config->announcementContent : '';
$workbench->config->enableAiRewrite = isset($workbench->config->enableAiRewrite) ? intval($workbench->config->enableAiRewrite) : 0;
$workbench->config->aiRewritePrompt = isset($workbench->config->aiRewritePrompt) ? $workbench->config->aiRewritePrompt : '';
}
} else {
// 好友推送
$workbench->config->wechatFriends = json_decode($workbench->config->friends, true) ?: [];
$workbench->config->wechatGroups = [];
$workbench->config->trafficPools = json_decode($workbench->config->trafficPools ?? '[]', true) ?: [];
}
$workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true);
$workbench->config->postPushTags = json_decode($workbench->config->postPushTags ?? '[]', true) ?: [];
unset($workbench->groupPush, $workbench->group_push);
}
break;
//建群助手
case self::TYPE_GROUP_CREATE:
if (!empty($workbench->groupCreate)) {
$workbench->config = $workbench->groupCreate;
$workbench->config->deviceGroups = json_decode($workbench->config->devices, true);
$workbench->config->poolGroups = json_decode($workbench->config->poolGroups, true);
$workbench->config->wechatGroups = json_decode($workbench->config->wechatGroups, true);
$workbench->config->admins = json_decode($workbench->config->admins ?? '[]', true) ?: [];
// 处理群管理员相关字段
$workbench->config->groupAdminEnabled = !empty($workbench->config->admins) ? 1 : 0;
// 如果有管理员设置groupAdminWechatId为第一个管理员的ID用于前端回显
$workbench->config->groupAdminWechatId = !empty($workbench->config->admins) ? $workbench->config->admins[0] : null;
// 统计已建群数状态为成功且groupId不为空的记录按groupId分组去重
$createdGroupsCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('status', 2) // STATUS_SUCCESS = 2
->where('groupId', '<>', null)
->group('groupId')
->count();
// 统计总人数(该工作台的所有记录数)
$totalMembersCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->count();
// 添加统计信息
$workbench->config->stats = [
'createdGroupsCount' => $createdGroupsCount,
'totalMembersCount' => $totalMembersCount
];
unset($workbench->groupCreate, $workbench->group_create);
}
break;
//流量分发
case self::TYPE_TRAFFIC_DISTRIBUTION:
if (!empty($workbench->trafficConfig)) {
$workbench->config = $workbench->trafficConfig;
$workbench->config->deviceGroups = json_decode($workbench->config->devices, true);
$workbench->config->accountGroups = json_decode($workbench->config->account, true);
$workbench->config->poolGroups = json_decode($workbench->config->pools, true);
$config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->find();
$workbench->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--';
//统计
$labels = $workbench->config->poolGroups;
$totalUsers = Db::table('s2_wechat_friend')->alias('wf')
->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
->where([
['wf.isDeleted', '=', 0],
['sa.departmentId', '=', $workbench->companyId]
])
->whereIn('wa.currentDeviceId', $workbench->config->deviceGroups)
->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId')
->where(function ($q) use ($labels) {
foreach ($labels as $label) {
$q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')");
}
})->count();
$totalAccounts = Db::table('s2_company_account')
->alias('a')
->where(['a.departmentId' => $workbench->companyId, 'a.status' => 0])
->whereNotLike('a.userName', '%_offline%')
->whereNotLike('a.userName', '%_delete%')
->group('a.id')
->count();
$dailyAverage = Db::name('workbench_traffic_config_item')
->where('workbenchId', $workbench->id)
->count();
$day = (time() - strtotime($workbench->createTime)) / 86400;
$day = intval($day);
if ($dailyAverage > 0) {
$dailyAverage = $dailyAverage / $totalAccounts / $day;
}
$workbench->config->total = [
'dailyAverage' => intval($dailyAverage),
'totalAccounts' => $totalAccounts,
'deviceCount' => count($workbench->config->deviceGroups),
'poolCount' => count($workbench->config->poolGroups),
'totalUsers' => $totalUsers >> 0
];
unset($workbench->trafficConfig, $workbench->traffic_config);
}
break;
case self::TYPE_IMPORT_CONTACT:
if (!empty($workbench->importContact)) {
$workbench->config = $workbench->importContact;
$workbench->config->deviceGroups = json_decode($workbench->config->devices, true);
$workbench->config->poolGroups = json_decode($workbench->config->pools, true);
}
unset($workbench->importContact, $workbench->import_contact);
break;
}
unset(
$workbench->autoLike,
$workbench->momentsSync,
$workbench->groupPush,
$workbench->groupCreate,
$workbench->config->devices,
$workbench->config->friends,
$workbench->config->groups,
$workbench->config->contentLibraries,
$workbench->config->account,
);
//获取设备信息
if (!empty($workbench->config->deviceGroups)) {
$deviceList = DeviceModel::alias('d')
->field([
'd.id', 'd.imei', 'd.memo', 'd.alive',
'l.wechatId',
'a.nickname', 'a.alias', 'a.avatar', 'a.alias', '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')
->whereIn('d.id', $workbench->config->deviceGroups)
->order('d.id desc')
->select();
foreach ($deviceList as &$device) {
$curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find();
$device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0;
}
unset($device);
$workbench->config->deviceGroupsOptions = $deviceList;
} else {
$workbench->config->deviceGroupsOptions = [];
}
// 获取群当targetType=1时
if (!empty($workbench->config->wechatGroups) && isset($workbench->config->targetType) && $workbench->config->targetType == 1) {
$groupList = Db::name('wechat_group')->alias('wg')
->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId')
->where('wg.id', 'in', $workbench->config->wechatGroups)
->order('wg.id', 'desc')
->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wa.avatar,wa.alias,wg.avatar as groupAvatar')
->select();
$workbench->config->wechatGroupsOptions = $groupList;
} else {
$workbench->config->wechatGroupsOptions = [];
}
// 获取好友当targetType=2时
if (!empty($workbench->config->wechatFriends) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) {
$friendList = Db::table('s2_wechat_friend')->alias('wf')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
->where('wf.id', 'in', $workbench->config->wechatFriends)
->order('wf.id', 'desc')
->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar')
->select();
$workbench->config->wechatFriendsOptions = $friendList;
} else {
$workbench->config->wechatFriendsOptions = [];
}
// 获取流量池当targetType=2时
if (!empty($workbench->config->trafficPools) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) {
$poolList = Db::name('traffic_source_package')->alias('tsp')
->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0')
->whereIn('tsp.id', $workbench->config->trafficPools)
->where('tsp.isDel', 0)
->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0])
->field('tsp.id,tsp.name,tsp.description,tsp.pic,COUNT(tspi.id) as itemCount')
->group('tsp.id')
->order('tsp.id', 'desc')
->select();
$workbench->config->trafficPoolsOptions = $poolList;
} else {
$workbench->config->trafficPoolsOptions = [];
}
// 获取内容库名称
if (!empty($workbench->config->contentGroups)) {
$libraryNames = ContentLibrary::where('id', 'in', $workbench->config->contentGroups)->select();
$workbench->config->contentGroupsOptions = $libraryNames;
} else {
$workbench->config->contentGroupsOptions = [];
}
//账号
if (!empty($workbench->config->accountGroups)) {
$account = Db::table('s2_company_account')->alias('a')
->where(['a.departmentId' => $this->request->userInfo['companyId'], 'a.status' => 0])
->whereIn('a.id', $workbench->config->accountGroups)
->whereNotLike('a.userName', '%_offline%')
->whereNotLike('a.userName', '%_delete%')
->field('a.id,a.userName,a.realName,a.nickname,a.memo')
->select();
$workbench->config->accountGroupsOptions = $account;
} else {
$workbench->config->accountGroupsOptions = [];
}
if (!empty($workbench->config->poolGroups)) {
$poolGroupsOptions = Db::name('traffic_source_package')->alias('tsp')
->join('traffic_source_package_item tspi', 'tspi.packageId=tsp.id', 'left')
->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0])
->whereIn('tsp.id', $workbench->config->poolGroups)
->field('tsp.id,tsp.name,tsp.description,tsp.createTime,count(tspi.id) as num')
->group('tsp.id')
->select();
$workbench->config->poolGroupsOptions = $poolGroupsOptions;
} else {
$workbench->config->poolGroupsOptions = [];
}
if (!empty($workbench->config->ownerWechatIds)) {
$ownerWechatOptions = Db::name('wechat_account')
->whereIn('id', $workbench->config->ownerWechatIds)
->field('id,wechatId,nickName,avatar,alias')
->select();
$workbench->config->ownerWechatOptions = $ownerWechatOptions;
} else {
$workbench->config->ownerWechatOptions = [];
}
// 获取群组选项(自动建群)
if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->wechatGroups)) {
// 分离数字ID好友ID和字符串ID手动创建的群组
$friendIds = [];
$manualGroupIds = [];
foreach ($workbench->config->wechatGroups as $groupId) {
if (is_numeric($groupId)) {
$friendIds[] = intval($groupId);
} else {
$manualGroupIds[] = $groupId;
}
}
$wechatGroupsOptions = [];
// 查询好友信息数字ID
if (!empty($friendIds)) {
$friendList = Db::table('s2_wechat_friend')->alias('wf')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
->join(['s2_company_account' => 'ca'], 'ca.id = wf.accountId', 'left')
->where('wf.id', 'in', $friendIds)
->order('wf.id', 'desc')
->field('wf.id,wf.wechatId,wf.nickname,wf.avatar,wf.alias,wf.gender,wf.phone,wa.nickName as accountNickname,ca.userName as account,ca.realName as username,wf.createTime,wf.updateTime,wf.deleteTime,wf.ownerWechatId')
->select();
// 获取群主信息
foreach ($friendList as &$friend) {
if (!empty($friend['ownerWechatId'])) {
$owner = Db::name('wechat_account')
->where('wechatId', $friend['ownerWechatId'])
->field('nickName,alias')
->find();
$friend['ownerNickname'] = $owner['nickName'] ?? '';
$friend['ownerAlias'] = $owner['alias'] ?? '';
} else {
$friend['ownerNickname'] = '';
$friend['ownerAlias'] = '';
}
$friend['isManual'] = '';
// 格式化时间
$friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : '';
$friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : '';
$friend['deleteTime'] = !empty($friend['deleteTime']) ? date('Y-m-d H:i:s', $friend['deleteTime']) : '';
}
unset($friend);
$wechatGroupsOptions = array_merge($wechatGroupsOptions, $friendList);
}
// 处理手动创建的群组字符串ID
if (!empty($manualGroupIds)) {
foreach ($manualGroupIds as $groupId) {
// 手动创建的群组,只返回基本信息
$wechatGroupsOptions[] = [
'id' => $groupId,
'wechatId' => $groupId,
'nickname' => $groupId,
'avatar' => '',
'isManual' => 1
];
}
}
$workbench->config->wechatGroupsOptions = $wechatGroupsOptions;
} else {
$workbench->config->wechatGroupsOptions = [];
}
// 获取管理员选项(自动建群)
if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->admins)) {
$adminOptions = Db::table('s2_wechat_friend')->alias('wf')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left')
->where('wf.id', 'in', $workbench->config->admins)
->order('wf.id', 'desc')
->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar')
->select();
$workbench->config->adminsOptions = $adminOptions;
} else {
$workbench->config->adminsOptions = [];
}
return json(['code' => 200, 'msg' => '获取成功', 'data' => $workbench]);
}
/**
* 更新工作台
* @return \think\response\Json
*/
public function update()
{
if (!$this->request->isPost()) {
return json(['code' => 400, 'msg' => '请求方式错误']);
}
// 获取请求参数
$param = $this->request->post();
// 验证数据
$validate = new WorkbenchValidate;
if (!$validate->scene('update')->check($param)) {
return json(['code' => 400, 'msg' => $validate->getError()]);
}
$where = [
['id', '=', $param['id']],
['companyId', '=', $this->request->userInfo['companyId']],
['isDel', '=', 0]
];
if (empty($this->request->userInfo['isAdmin'])) {
$where[] = ['userId', '=', $this->request->userInfo['id']];
}
// 查询工作台是否存在
$workbench = Workbench::where($where)->find();
if (!$workbench) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
Db::startTrans();
try {
// 更新工作台基本信息
$workbench->name = $param['name'];
$workbench->status = !empty($param['status']) ? 1 : 0;
$workbench->autoStart = !empty($param['autoStart']) ? 1 : 0;
$workbench->updateTime = time();
$workbench->save();
// 根据类型更新对应的配置
switch ($workbench->type) {
case self::TYPE_AUTO_LIKE:
$config = WorkbenchAutoLike::where('workbenchId', $param['id'])->find();
if ($config) {
$config->interval = $param['interval'];
$config->maxLikes = $param['maxLikes'];
$config->startTime = $param['startTime'];
$config->endTime = $param['endTime'];
$config->contentTypes = json_encode($param['contentTypes']);
$config->devices = json_encode($param['deviceGroups']);
$config->friends = json_encode($param['wechatFriends']);
// $config->targetGroups = json_encode($param['targetGroups']);
// $config->tagOperator = $param['tagOperator'];
$config->friendMaxLikes = $param['friendMaxLikes'];
$config->friendTags = $param['friendTags'];
$config->enableFriendTags = $param['enableFriendTags'];
$config->updateTime = time();
$config->save();
}
break;
case self::TYPE_MOMENTS_SYNC:
$config = WorkbenchMomentsSync::where('workbenchId', $param['id'])->find();
if ($config) {
if (!empty($param['contentGroups'])) {
foreach ($param['contentGroups'] as $library) {
if (isset($library['id']) && !empty($library['id'])) {
$contentLibraries[] = $library['id'];
} else {
$contentLibraries[] = $library;
}
}
} else {
$contentLibraries = [];
}
$config->syncInterval = $param['syncInterval'];
$config->syncCount = $param['syncCount'];
$config->syncType = $param['syncType'];
$config->startTime = $param['startTime'];
$config->endTime = $param['endTime'];
$config->accountType = $param['accountType'];
$config->devices = json_encode($param['deviceGroups']);
$config->contentLibraries = json_encode($contentLibraries);
$config->updateTime = time();
$config->save();
}
break;
case self::TYPE_GROUP_PUSH:
$config = WorkbenchGroupPush::where('workbenchId', $param['id'])->find();
if ($config) {
$ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? null, $config, $param['deviceGroups'] ?? []);
$groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds, $config);
$groupPushData['updateTime'] = time();
$config->save($groupPushData);
}
break;
case self::TYPE_GROUP_CREATE:
$config = WorkbenchGroupCreate::where('workbenchId', $param['id'])->find();
if ($config) {
$config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE);
$config->startTime = $param['startTime'] ?? '';
$config->endTime = $param['endTime'] ?? '';
$config->groupSizeMin = intval($param['groupSizeMin'] ?? 3);
$config->groupSizeMax = intval($param['groupSizeMax'] ?? 38);
$config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20);
$config->groupNameTemplate = $param['groupNameTemplate'] ?? '';
$config->groupDescription = $param['groupDescription'] ?? '';
$config->poolGroups = json_encode($param['poolGroups'] ?? [], JSON_UNESCAPED_UNICODE);
$config->wechatGroups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE);
// 处理群管理员如果启用了群管理员且有指定管理员则保存到admins字段
$admins = [];
if (!empty($param['groupAdminEnabled']) && !empty($param['groupAdminWechatId'])) {
// 如果groupAdminWechatId是数组取第一个如果是单个值直接使用
$adminWechatId = is_array($param['groupAdminWechatId']) ? $param['groupAdminWechatId'][0] : $param['groupAdminWechatId'];
// 如果是好友ID直接添加到admins如果是wechatId需要转换为好友ID
if (is_numeric($adminWechatId)) {
$admins[] = intval($adminWechatId);
} else {
// 如果是wechatId字符串需要查询对应的好友ID
$friend = Db::table('s2_wechat_friend')->where('wechatId', $adminWechatId)->find();
if ($friend) {
$admins[] = intval($friend['id']);
}
}
}
// 如果传入了admins参数优先使用兼容旧逻辑
if (!empty($param['admins']) && is_array($param['admins'])) {
$admins = array_merge($admins, $param['admins']);
}
$config->admins = json_encode(array_unique($admins), JSON_UNESCAPED_UNICODE);
$config->updateTime = time();
$config->save();
}
break;
case self::TYPE_TRAFFIC_DISTRIBUTION:
$config = WorkbenchTrafficConfig::where('workbenchId', $param['id'])->find();
if ($config) {
$config->distributeType = $param['distributeType'];
$config->maxPerDay = $param['maxPerDay'];
$config->timeType = $param['timeType'];
$config->startTime = $param['startTime'];
$config->endTime = $param['endTime'];
$config->devices = json_encode($param['deviceGroups']);
$config->pools = json_encode($param['poolGroups']);
$config->account = json_encode($param['accountGroups']);
$config->updateTime = time();
$config->save();
}
break;
case self::TYPE_IMPORT_CONTACT: //联系人导入
$config = WorkbenchImportContact::where('workbenchId', $param['id'])->find();;
if ($config) {
$config->devices = json_encode($param['deviceGroups']);
$config->pools = json_encode($param['poolGroups']);
$config->num = $param['num'];
$config->clearContact = $param['clearContact'];
$config->remark = $param['remark'];
$config->startTime = $param['startTime'];
$config->endTime = $param['endTime'];
$config->save();
}
break;
}
Db::commit();
return json(['code' => 200, 'msg' => '更新成功']);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
}
}
/**
* 更新工作台状态
* @return \think\response\Json
*/
public function updateStatus()
{
if (!$this->request->isPost()) {
return json(['code' => 400, 'msg' => '请求方式错误']);
}
$id = $this->request->param('id', '');
if (empty($id)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
$where = [
['id', '=', $id],
['companyId', '=', $this->request->userInfo['companyId']],
['isDel', '=', 0]
];
if (empty($this->request->userInfo['isAdmin'])) {
$where[] = ['userId', '=', $this->request->userInfo['id']];
}
$workbench = Workbench::where($where)->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
$workbench->status = !$workbench['status'];
$workbench->save();
return json(['code' => 200, 'msg' => '更新成功']);
}
/**
* 删除工作台(软删除)
*/
public function delete()
{
$id = $this->request->param('id');
if (empty($id)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
$where = [
['id', '=', $id],
['companyId', '=', $this->request->userInfo['companyId']],
['isDel', '=', 0]
];
if (empty($this->request->userInfo['isAdmin'])) {
$where[] = ['userId', '=', $this->request->userInfo['id']];
}
$workbench = Workbench::where($where)->find();
if (!$workbench) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
// 软删除
$workbench->isDel = 1;
$workbench->deleteTime = time();
$workbench->save();
return json(['code' => 200, 'msg' => '删除成功']);
}
/**
* 拷贝工作台
* @return \think\response\Json
*/
public function copy()
{
if (!$this->request->isPost()) {
return json(['code' => 400, 'msg' => '请求方式错误']);
}
$id = $this->request->post('id');
if (empty($id)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
// 验证权限并获取原数据
$workbench = Workbench::where([
['id', '=', $id],
['userId', '=', $this->request->userInfo['id']]
])->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
Db::startTrans();
try {
// 创建新的工作台基本信息
$newWorkbench = new Workbench;
$newWorkbench->name = $workbench->name . ' copy';
$newWorkbench->type = $workbench->type;
$newWorkbench->status = 1; // 新拷贝的默认启用
$newWorkbench->autoStart = $workbench->autoStart;
$newWorkbench->userId = $this->request->userInfo['id'];
$newWorkbench->companyId = $this->request->userInfo['companyId'];
$newWorkbench->save();
// 根据类型拷贝对应的配置
switch ($workbench->type) {
case self::TYPE_AUTO_LIKE:
$config = WorkbenchAutoLike::where('workbenchId', $id)->find();
if ($config) {
$newConfig = new WorkbenchAutoLike;
$newConfig->workbenchId = $newWorkbench->id;
$newConfig->interval = $config->interval;
$newConfig->maxLikes = $config->maxLikes;
$newConfig->startTime = $config->startTime;
$newConfig->endTime = $config->endTime;
$newConfig->contentTypes = $config->contentTypes;
$newConfig->devices = $config->devices;
$newConfig->friends = $config->friends;
$newConfig->createTime = time();
$newConfig->updateTime = time();
$newConfig->save();
}
break;
case self::TYPE_MOMENTS_SYNC:
$config = WorkbenchMomentsSync::where('workbenchId', $id)->find();
if ($config) {
$newConfig = new WorkbenchMomentsSync;
$newConfig->workbenchId = $newWorkbench->id;
$newConfig->syncInterval = $config->syncInterval;
$newConfig->syncCount = $config->syncCount;
$newConfig->syncType = $config->syncType;
$newConfig->startTime = $config->startTime;
$newConfig->endTime = $config->endTime;
$newConfig->accountType = $config->accountType;
$newConfig->devices = $config->devices;
$newConfig->contentLibraries = $config->contentLibraries;
$newConfig->createTime = time();
$newConfig->updateTime = time();
$newConfig->save();
}
break;
case self::TYPE_GROUP_PUSH:
$config = WorkbenchGroupPush::where('workbenchId', $id)->find();
if ($config) {
$newConfig = new WorkbenchGroupPush;
$newConfig->workbenchId = $newWorkbench->id;
$newConfig->pushType = $config->pushType;
$newConfig->targetType = isset($config->targetType) ? $config->targetType : 1; // 默认1=群推送
$newConfig->startTime = $config->startTime;
$newConfig->endTime = $config->endTime;
$newConfig->maxPerDay = $config->maxPerDay;
$newConfig->pushOrder = $config->pushOrder;
$newConfig->isLoop = $config->isLoop;
$newConfig->status = $config->status;
$newConfig->groups = $config->groups;
$newConfig->friends = $config->friends;
$newConfig->contentLibraries = $config->contentLibraries;
$newConfig->trafficPools = property_exists($config, 'trafficPools') ? $config->trafficPools : json_encode([], JSON_UNESCAPED_UNICODE);
$newConfig->socialMediaId = $config->socialMediaId;
$newConfig->promotionSiteId = $config->promotionSiteId;
$newConfig->ownerWechatIds = $config->ownerWechatIds;
$newConfig->createTime = time();
$newConfig->updateTime = time();
$newConfig->save();
}
break;
case self::TYPE_GROUP_CREATE:
$config = WorkbenchGroupCreate::where('workbenchId', $id)->find();
if ($config) {
$newConfig = new WorkbenchGroupCreate;
$newConfig->workbenchId = $newWorkbench->id;
$newConfig->devices = $config->devices;
$newConfig->startTime = $config->startTime;
$newConfig->endTime = $config->endTime;
$newConfig->groupSizeMin = $config->groupSizeMin;
$newConfig->groupSizeMax = $config->groupSizeMax;
$newConfig->maxGroupsPerDay = $config->maxGroupsPerDay;
$newConfig->groupNameTemplate = $config->groupNameTemplate;
$newConfig->groupDescription = $config->groupDescription;
$newConfig->poolGroups = $config->poolGroups;
$newConfig->wechatGroups = $config->wechatGroups;
$newConfig->admins = $config->admins ?? json_encode([], JSON_UNESCAPED_UNICODE);
$newConfig->createTime = time();
$newConfig->updateTime = time();
$newConfig->save();
}
break;
case self::TYPE_IMPORT_CONTACT: //联系人导入
$config = WorkbenchImportContact::where('workbenchId', $id)->find();
if ($config) {
$newConfig = new WorkbenchImportContact;
$newConfig->workbenchId = $newWorkbench->id;
$newConfig->devices = $config->devices;
$newConfig->pools = $config->pools;
$newConfig->num = $config->num;
$newConfig->clearContact = $config->clearContact;
$newConfig->remark = $config->remark;
$newConfig->startTime = $config->startTime;
$newConfig->endTime = $config->endTime;
$newConfig->createTime = time();
$newConfig->save();
}
break;
}
Db::commit();
return json(['code' => 200, 'msg' => '拷贝成功', 'data' => ['id' => $newWorkbench->id]]);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 500, 'msg' => '拷贝失败:' . $e->getMessage()]);
}
}
/**
* 获取点赞记录列表
* @return \think\response\Json
*/
public function getLikeRecords()
{
$controller = new \app\cunkebao\controller\workbench\WorkbenchAutoLikeController();
$controller->request = $this->request;
return $controller->getLikeRecords();
}
/**
* 获取朋友圈发布记录列表
* @return \think\response\Json
*/
public function getMomentsRecords()
{
$controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController();
$controller->request = $this->request;
return $controller->getMomentsRecords();
}
/**
* 获取朋友圈发布统计
* @return \think\response\Json
*/
public function getMomentsStats()
{
$controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController();
$controller->request = $this->request;
return $controller->getMomentsStats();
}
/**
* 获取流量分发记录列表
* @return \think\response\Json
*/
public function getTrafficDistributionRecords()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$workbenchId = $this->request->param('workbenchId', 0);
$where = [
['wtdi.workbenchId', '=', $workbenchId]
];
// 查询分发记录
$list = Db::name('workbench_traffic_distribution_item')->alias('wtdi')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left')
->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left')
->field([
'wtdi.id',
'wtdi.workbenchId',
'wtdi.wechatAccountId',
'wtdi.wechatFriendId',
'wtdi.createTime as distributeTime',
'wtdi.status',
'wtdi.errorMsg',
'wa.nickName as operatorName',
'wa.avatar as operatorAvatar',
'wf.nickName as friendName',
'wf.avatar as friendAvatar',
'wf.gender',
'wf.province',
'wf.city'
])
->where($where)
->order('wtdi.createTime', 'desc')
->page($page, $limit)
->select();
// 处理数据
foreach ($list as &$item) {
// 处理时间格式
$item['distributeTime'] = date('Y-m-d H:i:s', $item['distributeTime']);
// 处理性别
$genderMap = [
0 => '未知',
1 => '男',
2 => '女'
];
$item['genderText'] = $genderMap[$item['gender']] ?? '未知';
// 处理状态文字
$statusMap = [
0 => '待分发',
1 => '分发成功',
2 => '分发失败'
];
$item['statusText'] = $statusMap[$item['status']] ?? '未知状态';
}
// 获取总记录数
$total = Db::name('workbench_traffic_distribution_item')->alias('wtdi')
->where($where)
->count();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $list,
'total' => $total,
'page' => $page,
'limit' => $limit
]
]);
}
/**
* 获取流量分发统计
* @return \think\response\Json
*/
public function getTrafficDistributionStats()
{
$workbenchId = $this->request->param('workbenchId', 0);
if (empty($workbenchId)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
// 获取今日数据
$todayStart = strtotime(date('Y-m-d') . ' 00:00:00');
$todayEnd = strtotime(date('Y-m-d') . ' 23:59:59');
$todayStats = Db::name('workbench_traffic_distribution_item')
->where([
['workbenchId', '=', $workbenchId],
['createTime', 'between', [$todayStart, $todayEnd]]
])
->field([
'COUNT(*) as total',
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success',
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed'
])
->find();
// 获取总数据
$totalStats = Db::name('workbench_traffic_distribution_item')
->where('workbenchId', $workbenchId)
->field([
'COUNT(*) as total',
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success',
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed'
])
->find();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'today' => [
'total' => intval($todayStats['total']),
'success' => intval($todayStats['success']),
'failed' => intval($todayStats['failed'])
],
'total' => [
'total' => intval($totalStats['total']),
'success' => intval($totalStats['success']),
'failed' => intval($totalStats['failed'])
]
]
]);
}
/**
* 获取流量分发详情
* @return \think\response\Json
*/
public function getTrafficDistributionDetail()
{
$id = $this->request->param('id', 0);
if (empty($id)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
$detail = Db::name('workbench_traffic_distribution_item')->alias('wtdi')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left')
->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left')
->field([
'wtdi.id',
'wtdi.workbenchId',
'wtdi.wechatAccountId',
'wtdi.wechatFriendId',
'wtdi.createTime as distributeTime',
'wtdi.status',
'wtdi.errorMsg',
'wa.nickName as operatorName',
'wa.avatar as operatorAvatar',
'wf.nickName as friendName',
'wf.avatar as friendAvatar',
'wf.gender',
'wf.province',
'wf.city',
'wf.signature',
'wf.remark'
])
->where('wtdi.id', $id)
->find();
if (empty($detail)) {
return json(['code' => 404, 'msg' => '记录不存在']);
}
// 处理数据
$detail['distributeTime'] = date('Y-m-d H:i:s', $detail['distributeTime']);
// 处理性别
$genderMap = [
0 => '未知',
1 => '男',
2 => '女'
];
$detail['genderText'] = $genderMap[$detail['gender']] ?? '未知';
// 处理状态文字
$statusMap = [
0 => '待分发',
1 => '分发成功',
2 => '分发失败'
];
$detail['statusText'] = $statusMap[$detail['status']] ?? '未知状态';
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $detail
]);
}
/**
* 创建流量分发计划
* @return \think\response\Json
*/
public function createTrafficPlan()
{
$param = $this->request->post();
Db::startTrans();
try {
// 1. 创建主表
$planId = Db::name('ck_workbench')->insertGetId([
'name' => $param['name'],
'type' => self::TYPE_TRAFFIC_DISTRIBUTION,
'status' => 1,
'autoStart' => $param['autoStart'] ?? 0,
'userId' => $this->request->userInfo['id'],
'companyId' => $this->request->userInfo['companyId'],
'createTime' => time(),
'updateTime' => time()
]);
// 2. 创建扩展表
Db::name('ck_workbench_traffic_config')->insert([
'workbenchId' => $planId,
'distributeType' => $param['distributeType'],
'maxPerDay' => $param['maxPerDay'],
'timeType' => $param['timeType'],
'startTime' => $param['startTime'],
'endTime' => $param['endTime'],
'targets' => json_encode($param['targets'], JSON_UNESCAPED_UNICODE),
'pools' => json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE),
'createTime' => time(),
'updateTime' => time()
]);
Db::commit();
return json(['code' => 200, 'msg' => '创建成功']);
} catch (\Exception $e) {
Db::rollback();
return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]);
}
}
/**
* 获取所有微信好友标签及数量统计
* @return \think\response\Json
*/
public function getDeviceLabels()
{
$deviceIds = $this->request->param('deviceIds', '');
$companyId = $this->request->userInfo['companyId'];
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$where = [
['wc.companyId', '=', $companyId],
];
if (!empty($deviceIds)) {
$deviceIds = explode(',', $deviceIds);
$where[] = ['dwl.deviceId', 'in', $deviceIds];
}
$wechatAccounts = Db::name('wechat_customer')->alias('wc')
->join('device_wechat_login dwl', 'dwl.wechatId = wc.wechatId AND dwl.companyId = wc.companyId AND dwl.alive = 1')
->join(['s2_wechat_account' => 'wa'], 'wa.wechatId = wc.wechatId')
->where($where)
->field('wa.id,wa.wechatId,wa.nickName,wa.labels')
->select();
$labels = [];
$wechatIds = [];
foreach ($wechatAccounts as $account) {
$labelArr = json_decode($account['labels'], true);
if (is_array($labelArr)) {
foreach ($labelArr as $label) {
if ($label !== '' && $label !== null) {
$labels[] = $label;
}
}
}
$wechatIds[] = $account['wechatId'];
}
// 去重(只保留一个)
$labels = array_values(array_unique($labels));
$wechatIds = array_unique($wechatIds);
// 搜索过滤
if (!empty($keyword)) {
$labels = array_filter($labels, function ($label) use ($keyword) {
return mb_stripos($label, $keyword) !== false;
});
$labels = array_values($labels); // 重新索引数组
}
// 分页处理
$labels2 = array_slice($labels, ($page - 1) * $limit, $limit);
// 统计数量
$newLabel = [];
foreach ($labels2 as $label) {
$friendCount = Db::table('s2_wechat_friend')
->whereIn('ownerWechatId', $wechatIds)
->where('labels', 'like', '%"' . $label . '"%')
->count();
$newLabel[] = [
'label' => $label,
'count' => $friendCount
];
}
// 返回结果
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $newLabel,
'total' => count($labels),
]
]);
}
/**
* 获取群列表
* @return \think\response\Json
*/
public function getGroupList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$where = [
['wg.deleteTime', '=', 0],
['wg.companyId', '=', $this->request->userInfo['companyId']],
];
if (!empty($keyword)) {
$where[] = ['wg.name', 'like', '%' . $keyword . '%'];
}
$query = Db::name('wechat_group')->alias('wg')
->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId')
->where($where);
$total = $query->count();
$list = $query->order('wg.id', 'desc')
->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wg.createTime,wa.avatar,wa.alias,wg.avatar as groupAvatar')
->page($page, $limit)
->select();
// 优化:格式化时间,头像兜底
$defaultGroupAvatar = '';
$defaultAvatar = '';
foreach ($list as &$item) {
$item['createTime'] = $item['createTime'] ? date('Y-m-d H:i:s', $item['createTime']) : '';
$item['groupAvatar'] = $item['groupAvatar'] ?: $defaultGroupAvatar;
$item['avatar'] = $item['avatar'] ?: $defaultAvatar;
}
return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]);
}
/**
* 获取流量池列表
* @return \think\response\Json
*/
public function getTrafficPoolList()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$companyId = $this->request->userInfo['companyId'];
$baseQuery = Db::name('traffic_source_package')->alias('tsp')
->where('tsp.isDel', 0)
->whereIn('tsp.companyId', [$companyId, 0]);
if (!empty($keyword)) {
$baseQuery->whereLike('tsp.name', '%' . $keyword . '%');
}
$total = (clone $baseQuery)->count();
$list = $baseQuery
->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0')
->field('tsp.id,tsp.name,tsp.description,tsp.pic,tsp.companyId,COUNT(tspi.id) as itemCount,max(tspi.createTime) as latestImportTime')
->group('tsp.id')
->order('tsp.id', 'desc')
->page($page, $limit)
->select();
foreach ($list as &$item) {
$item['latestImportTime'] = !empty($item['latestImportTime']) ? date('Y-m-d H:i:s', $item['latestImportTime']) : '';
}
unset($item);
return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]);
}
public function getAccountList()
{
$companyId = $this->request->userInfo['companyId'];
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$query = Db::table('s2_company_account')
->alias('a')
->where(['a.departmentId' => $companyId, 'a.status' => 0])
->whereNotLike('a.userName', '%_offline%')
->whereNotLike('a.userName', '%_delete%');
$total = $query->count();
$list = $query->field('a.id,a.userName,a.realName,a.nickname,a.memo')
->page($page, $limit)
->select();
return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]);
}
/**
* 获取京东联盟导购媒体
* @return \think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getJdSocialMedia()
{
$data = Db::name('jd_social_media')->order('id DESC')->select();
return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
}
/**
* 获取京东联盟广告位
* @return \think\response\Json
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function getJdPromotionSite()
{
$id = $this->request->param('id', '');
if (empty($id)) {
return json(['code' => 500, 'msg' => '参数缺失']);
}
$data = Db::name('jd_promotion_site')->where('jdSocialMediaId', $id)->order('id DESC')->select();
return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
}
//京东转链-京推推
public function changeLink($content = '', $positionid = '')
{
$unionId = Env::get('jd.unionId', '');
$jttAppId = Env::get('jd.jttAppId', '');
$appKey = Env::get('jd.appKey', '');
$apiUrl = Env::get('jd.apiUrl', '');
$content = !empty($content) ? $content : $this->request->param('content', '');
$positionid = !empty($positionid) ? $positionid : $this->request->param('positionid', '');
if (empty($content)) {
return json_encode(['code' => 500, 'msg' => '转链的内容为空']);
}
// 验证是否包含链接
if (!$this->containsLink($content)) {
return json_encode(['code' => 500, 'msg' => '内容中未检测到有效链接']);
}
if (empty($unionId) || empty($jttAppId) || empty($appKey) || empty($apiUrl)) {
return json_encode(['code' => 500, 'msg' => '参数缺失']);
}
$params = [
'unionid' => $unionId,
'content' => $content,
'appid' => $jttAppId,
'appkey' => $appKey,
'v' => 'v2'
];
if (!empty($positionid)) {
$params['positionid'] = $positionid;
}
$res = requestCurl($apiUrl, $params, 'GET', [], 'json');
$res = json_decode($res, true);
if (empty($res)) {
return json_encode(['code' => 500, 'msg' => '未知错误']);
}
$result = $res['result'];
if ($res['return'] == 0) {
return json_encode(['code' => 200, 'data' => $result['chain_content'], 'msg' => $result['msg']]);
} else {
return json_encode(['code' => 500, 'msg' => $result['msg']]);
}
}
public function getTrafficList()
{
$companyId = $this->request->userInfo['companyId'];
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$keyword = $this->request->param('keyword', '');
$workbenchId = $this->request->param('workbenchId', '');
$isRecycle = $this->request->param('isRecycle', '');
if (empty($workbenchId)) {
return json(['code' => 400, 'msg' => '参数错误']);
}
$workbench = Db::name('workbench')->where(['id' => $workbenchId, 'isDel' => 0, 'companyId' => $companyId, 'type' => 5])->find();
if (empty($workbench)) {
return json(['code' => 400, 'msg' => '该任务不存在或已删除']);
}
$query = Db::name('workbench_traffic_config_item')->alias('wtc')
->join(['s2_wechat_friend' => 'wf'], 'wtc.wechatFriendId = wf.id')
->join('users u', 'wtc.wechatAccountId = u.s2_accountId', 'left')
->field([
'wtc.id', 'wtc.isRecycle', 'wtc.isRecycle', 'wtc.createTime','wtc.recycleTime',
'wf.wechatId', 'wf.alias', 'wf.nickname', 'wf.avatar', 'wf.gender', 'wf.phone',
'u.account', 'u.username'
])
->where(['wtc.workbenchId' => $workbenchId])
->order('wtc.id DESC');
if (!empty($keyword)) {
$query->where('wf.wechatId|wf.alias|wf.nickname|wf.phone|u.account|u.username', 'like', '%' . $keyword . '%');
}
if ($isRecycle != '' || $isRecycle != null) {
$query->where('isRecycle',$isRecycle);
}
$total = $query->count();
$list = $query->page($page, $limit)->select();
foreach ($list as &$item) {
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
$item['recycleTime'] = date('Y-m-d H:i:s', $item['recycleTime']);
}
unset($item);
$data = [
'total' => $total,
'list' => $list,
];
return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
}
/**
* 规范化客服微信ID列表
* @param mixed $ownerWechatIds
* @param WorkbenchGroupPush|null $originalConfig
* @param array $deviceGroups 设备ID数组
* @return array
* @throws \Exception
*/
private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null, array $deviceGroups = []): array
{
// 处理设备ID数组
$deviceGroupsList = $this->extractIdList($deviceGroups, '设备参数格式错误');
if ($ownerWechatIds === null) {
$existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : [];
// 如果原有配置为空且没有传设备ID则报错
if (empty($existing) && empty($deviceGroupsList)) {
throw new \Exception('请至少选择一个客服微信或设备');
}
// 如果原有配置为空但有设备ID返回空数组允许使用设备ID
if (empty($existing) && !empty($deviceGroupsList)) {
return [];
}
return $existing;
}
if (!is_array($ownerWechatIds)) {
throw new \Exception('客服参数格式错误');
}
$normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误');
// 如果 ownerWechatIds 为空但传了设备ID则允许两个传一个即可
if (empty($normalized) && empty($deviceGroupsList)) {
throw new \Exception('请至少选择一个客服微信或设备');
}
return $normalized;
}
/**
* 构建群推送配置数据
* @param array $param
* @param array $ownerWechatIds
* @param WorkbenchGroupPush|null $originalConfig
* @return array
* @throws \Exception
*/
private function prepareGroupPushData(array $param, array $ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array
{
$targetTypeDefault = $originalConfig ? intval($originalConfig->targetType) : 1;
$targetType = intval($this->getParamValue($param, 'targetType', $targetTypeDefault)) ?: 1;
$groupPushSubTypeDefault = $originalConfig ? intval($originalConfig->groupPushSubType) : 1;
$groupPushSubType = intval($this->getParamValue($param, 'groupPushSubType', $groupPushSubTypeDefault)) ?: 1;
if (!in_array($groupPushSubType, [1, 2], true)) {
$groupPushSubType = 1;
}
$data = [
'pushType' => $this->toBoolInt($this->getParamValue($param, 'pushType', $originalConfig->pushType ?? 0)),
'targetType' => $targetType,
'startTime' => $this->getParamValue($param, 'startTime', $originalConfig->startTime ?? ''),
'endTime' => $this->getParamValue($param, 'endTime', $originalConfig->endTime ?? ''),
'maxPerDay' => intval($this->getParamValue($param, 'maxPerDay', $originalConfig->maxPerDay ?? 0)),
'pushOrder' => $this->getParamValue($param, 'pushOrder', $originalConfig->pushOrder ?? 1),
'groupPushSubType' => $groupPushSubType,
'status' => $this->toBoolInt($this->getParamValue($param, 'status', $originalConfig->status ?? 0)),
'socialMediaId' => $this->getParamValue($param, 'socialMediaId', $originalConfig->socialMediaId ?? ''),
'promotionSiteId' => $this->getParamValue($param, 'promotionSiteId', $originalConfig->promotionSiteId ?? ''),
'friendIntervalMin' => intval($this->getParamValue($param, 'friendIntervalMin', $originalConfig->friendIntervalMin ?? 10)),
'friendIntervalMax' => intval($this->getParamValue($param, 'friendIntervalMax', $originalConfig->friendIntervalMax ?? 20)),
'messageIntervalMin' => intval($this->getParamValue($param, 'messageIntervalMin', $originalConfig->messageIntervalMin ?? 1)),
'messageIntervalMax' => intval($this->getParamValue($param, 'messageIntervalMax', $originalConfig->messageIntervalMax ?? 12)),
'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)),
'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE),
];
// 处理设备ID数组deviceGroups保存到 devices 字段
$deviceGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->devices ?? []) : [];
$deviceGroupsParam = $this->getParamValue($param, 'deviceGroups', null);
$deviceGroups = $deviceGroupsParam !== null
? $this->extractIdList($deviceGroupsParam, '设备参数格式错误')
: $deviceGroupsExisting;
$data['devices'] = json_encode($deviceGroups, JSON_UNESCAPED_UNICODE);
if ($data['friendIntervalMin'] > $data['friendIntervalMax']) {
throw new \Exception('目标间最小间隔不能大于最大间隔');
}
if ($data['messageIntervalMin'] > $data['messageIntervalMax']) {
throw new \Exception('消息间最小间隔不能大于最大间隔');
}
// 群公告groupPushSubType = 2contentGroups 可以为空,不需要验证
if ($targetType === 1 && $groupPushSubType === 2) {
// 群公告可以不传内容库,允许为空,不进行任何验证
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
if ($contentGroupsParam !== null) {
// 如果传入了参数,尝试解析,但不验证格式和是否为空
try {
$contentGroups = $this->extractIdList($contentGroupsParam, '内容库参数格式错误');
} catch (\Exception $e) {
// 群公告时忽略格式错误,使用空数组
$contentGroups = [];
}
} else {
// 如果没有传入参数,使用现有配置或空数组
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
$contentGroups = $contentGroupsExisting;
}
} else {
// 其他情况,正常处理并验证
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
$contentGroups = $contentGroupsParam !== null
? $this->extractIdList($contentGroupsParam, '内容库参数格式错误')
: $contentGroupsExisting;
// 其他情况,内容库为必填
if (empty($contentGroups)) {
throw new \Exception('请至少选择一个内容库');
}
}
$data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE);
$postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : [];
$postPushTagsParam = $this->getParamValue($param, 'postPushTags', null);
$postPushTags = $postPushTagsParam !== null
? $this->extractIdList($postPushTagsParam, '推送标签参数格式错误')
: $postPushTagsExisting;
$data['postPushTags'] = json_encode($postPushTags, JSON_UNESCAPED_UNICODE);
if ($targetType === 1) {
$data['isLoop'] = $this->toBoolInt($this->getParamValue($param, 'isLoop', $originalConfig->isLoop ?? 0));
$groupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->groups ?? []) : [];
$wechatGroups = array_key_exists('wechatGroups', $param)
? $this->extractIdList($param['wechatGroups'], '群参数格式错误')
: $groupsExisting;
if (empty($wechatGroups)) {
throw new \Exception('群推送必须选择微信群');
}
$data['groups'] = json_encode($wechatGroups, JSON_UNESCAPED_UNICODE);
$data['friends'] = json_encode([], JSON_UNESCAPED_UNICODE);
$data['trafficPools'] = json_encode([], JSON_UNESCAPED_UNICODE);
if ($groupPushSubType === 2) {
$announcementContent = $this->getParamValue($param, 'announcementContent', $originalConfig->announcementContent ?? '');
if (empty($announcementContent)) {
throw new \Exception('群公告必须输入公告内容');
}
$enableAiRewrite = $this->toBoolInt($this->getParamValue($param, 'enableAiRewrite', $originalConfig->enableAiRewrite ?? 0));
$aiRewritePrompt = trim((string)$this->getParamValue($param, 'aiRewritePrompt', $originalConfig->aiRewritePrompt ?? ''));
if ($enableAiRewrite === 1 && $aiRewritePrompt === '') {
throw new \Exception('启用AI智能话术改写时必须输入改写提示词');
}
$data['announcementContent'] = $announcementContent;
$data['enableAiRewrite'] = $enableAiRewrite;
$data['aiRewritePrompt'] = $aiRewritePrompt;
} else {
$data['groupPushSubType'] = 1;
$data['announcementContent'] = '';
$data['enableAiRewrite'] = 0;
$data['aiRewritePrompt'] = '';
}
} else {
$data['isLoop'] = 0;
$friendsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->friends ?? []) : [];
$trafficPoolsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->trafficPools ?? []) : [];
$friendTargets = array_key_exists('wechatFriends', $param)
? $this->extractIdList($param['wechatFriends'], '好友参数格式错误')
: $friendsExisting;
$trafficPools = array_key_exists('trafficPools', $param)
? $this->extractIdList($param['trafficPools'], '流量池参数格式错误')
: $trafficPoolsExisting;
if (empty($friendTargets) && empty($trafficPools)) {
throw new \Exception('好友推送需至少选择好友或流量池');
}
$data['friends'] = json_encode($friendTargets, JSON_UNESCAPED_UNICODE);
$data['trafficPools'] = json_encode($trafficPools, JSON_UNESCAPED_UNICODE);
$data['groups'] = json_encode([], JSON_UNESCAPED_UNICODE);
$data['groupPushSubType'] = 1;
$data['announcementContent'] = '';
$data['enableAiRewrite'] = 0;
$data['aiRewritePrompt'] = '';
}
return $data;
}
/**
* 获取参数值,若不存在则返回默认值
* @param array $param
* @param string $key
* @param mixed $default
* @return mixed
*/
private function getParamValue(array $param, string $key, $default)
{
return array_key_exists($key, $param) ? $param[$key] : $default;
}
/**
* 将值转换为整型布尔
* @param mixed $value
* @return int
*/
private function toBoolInt($value): int
{
return empty($value) ? 0 : 1;
}
/**
* 从参数中提取ID列表
* @param mixed $items
* @param string $errorMessage
* @return array
* @throws \Exception
*/
private function extractIdList($items, string $errorMessage = '参数格式错误'): array
{
if (!is_array($items)) {
throw new \Exception($errorMessage);
}
$ids = [];
foreach ($items as $item) {
if (is_array($item) && isset($item['id'])) {
$item = $item['id'];
}
if ($item === '' || $item === null) {
continue;
}
$ids[] = $item;
}
return array_values(array_unique($ids));
}
/**
* 解码JSON数组
* @param mixed $value
* @return array
*/
private function decodeJsonArray($value): array
{
if (empty($value)) {
return [];
}
if (is_array($value)) {
return $value;
}
$decoded = json_decode($value, true);
return is_array($decoded) ? $decoded : [];
}
/**
* 验证内容是否包含链接
* @param string $content 要检测的内容
* @return bool
*/
private function containsLink($content)
{
// 定义各种链接的正则表达式模式
$patterns = [
// HTTP/HTTPS链接
'/https?:\/\/[^\s]+/i',
// 京东商品链接
'/item\.jd\.com\/\d+/i',
// 京东短链接
'/u\.jd\.com\/[a-zA-Z0-9]+/i',
// 淘宝商品链接
'/item\.taobao\.com\/item\.htm\?id=\d+/i',
// 天猫商品链接
'/detail\.tmall\.com\/item\.htm\?id=\d+/i',
// 淘宝短链接
'/m\.tb\.cn\/[a-zA-Z0-9]+/i',
// 拼多多链接
'/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i',
// 苏宁易购链接
'/product\.suning\.com\/\d+\/\d+\.html/i',
// 通用域名模式(包含常见电商域名)
'/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i',
// 通用短链接模式
'/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i'
];
// 遍历所有模式进行匹配
foreach ($patterns as $pattern) {
if (preg_match($pattern, $content)) {
return true;
}
}
return false;
}
/**
* 获取群发统计数据
* @return \think\response\Json
*/
public function getGroupPushStats()
{
$workbenchId = $this->request->param('workbenchId', 0);
$timeRange = $this->request->param('timeRange', '7'); // 默认最近7天
$contentLibraryIds = $this->request->param('contentLibraryIds', ''); // 话术组筛选
$userId = $this->request->userInfo['id'];
// 如果指定了工作台ID则验证权限
if (!empty($workbenchId)) {
$workbench = Workbench::where([
['id', '=', $workbenchId],
['userId', '=', $userId],
['type', '=', self::TYPE_GROUP_PUSH],
['isDel', '=', 0]
])->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
}
// 计算时间范围
$days = intval($timeRange);
$startTime = strtotime(date('Y-m-d 00:00:00', strtotime("-{$days} days")));
$endTime = time();
// 构建查询条件
$where = [
['wgpi.createTime', '>=', $startTime],
['wgpi.createTime', '<=', $endTime]
];
// 如果指定了工作台ID则限制查询范围
if (!empty($workbenchId)) {
$where[] = ['wgpi.workbenchId', '=', $workbenchId];
} else {
// 如果没有指定工作台ID则查询当前用户的所有群推送工作台
$workbenchIds = Workbench::where([
['userId', '=', $userId],
['type', '=', self::TYPE_GROUP_PUSH],
['isDel', '=', 0]
])->column('id');
if (empty($workbenchIds)) {
// 如果没有工作台,返回空结果
$workbenchIds = [-1];
}
$where[] = ['wgpi.workbenchId', 'in', $workbenchIds];
}
// 话术组筛选 - 先获取符合条件的内容ID列表
$contentIds = null;
if (!empty($contentLibraryIds)) {
$libraryIds = is_array($contentLibraryIds) ? $contentLibraryIds : explode(',', $contentLibraryIds);
$libraryIds = array_filter(array_map('intval', $libraryIds));
if (!empty($libraryIds)) {
// 查询符合条件的内容ID
$contentIds = Db::name('content_item')
->whereIn('libraryId', $libraryIds)
->column('id');
if (empty($contentIds)) {
// 如果没有符合条件的内容,返回空结果
$contentIds = [-1]; // 使用不存在的ID确保查询结果为空
}
}
}
// 1. 基础统计:触达率、回复率、平均回复时间、链接点击率
$stats = $this->calculateBasicStats($workbenchId, $where, $startTime, $endTime, $contentIds);
// 2. 话术组对比
$contentLibraryComparison = $this->getContentLibraryComparison($workbenchId, $where, $startTime, $endTime, $contentIds);
// 3. 时段分析
$timePeriodAnalysis = $this->getTimePeriodAnalysis($workbenchId, $where, $startTime, $endTime, $contentIds);
// 4. 互动深度(可选,需要更多数据)
$interactionDepth = $this->getInteractionDepth($workbenchId, $where, $startTime, $endTime, $contentIds);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'basicStats' => $stats,
'contentLibraryComparison' => $contentLibraryComparison,
'timePeriodAnalysis' => $timePeriodAnalysis,
'interactionDepth' => $interactionDepth
]
]);
}
/**
* 计算基础统计数据
*/
private function calculateBasicStats($workbenchId, $where, $startTime, $endTime, $contentIds = null)
{
// 获取工作台配置,计算计划发送数
// 如果 workbenchId 为空,则查询所有工作台的配置
$configQuery = WorkbenchGroupPush::alias('wgp')
->join('workbench w', 'w.id = wgp.workbenchId', 'left')
->where('w.type', self::TYPE_GROUP_PUSH)
->where('w.isDel', 0);
if (!empty($workbenchId)) {
$configQuery->where('wgp.workbenchId', $workbenchId);
} else {
// 如果没有指定工作台ID需要从 where 条件中获取 workbenchId 列表
$workbenchIdCondition = null;
foreach ($where as $condition) {
if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') {
if ($condition[1] === 'in' && is_array($condition[2])) {
$workbenchIdCondition = $condition[2];
break;
} elseif ($condition[1] === '=') {
$workbenchIdCondition = [$condition[2]];
break;
}
}
}
if ($workbenchIdCondition) {
$configQuery->whereIn('wgp.workbenchId', $workbenchIdCondition);
}
}
$configs = $configQuery->select();
$targetType = 1; // 默认值
if (!empty($configs)) {
// 如果只有一个配置,使用它的 targetType如果有多个默认使用1
$targetType = intval($configs[0]->targetType ?? 1);
}
// 计划发送数(根据配置计算)
$plannedSend = 0;
if (!empty($configs)) {
$days = ceil(($endTime - $startTime) / 86400);
foreach ($configs as $config) {
$maxPerDay = intval($config->maxPerDay ?? 0);
$configTargetType = intval($config->targetType ?? 1);
if ($configTargetType == 1) {
// 群推送:计划发送数 = 每日推送次数 * 天数 * 群数量
$groups = $this->decodeJsonArray($config->groups ?? []);
$plannedSend += $maxPerDay * $days * count($groups);
} else {
// 好友推送:计划发送数 = 每日推送人数 * 天数
$plannedSend += $maxPerDay * $days;
}
}
}
// 构建查询条件
$queryWhere = $where;
if ($contentIds !== null) {
$queryWhere[] = ['wgpi.contentId', 'in', $contentIds];
}
// 实际成功发送数(从推送记录表统计)
$successSend = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($queryWhere)
->count();
// 触达率 = 成功发送数 / 计划发送数
$reachRate = $plannedSend > 0 ? round(($successSend / $plannedSend) * 100, 1) : 0;
// 获取发送记录列表,用于查询回复
$sentItemIds = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($queryWhere)
->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId')
->select();
// 回复统计(通过消息表查询)
$replyStats = $this->calculateReplyStats($sentItemIds, $targetType, $startTime, $endTime);
// 链接点击统计
$clickStats = $this->calculateClickStats($sentItemIds, $targetType, $startTime, $endTime);
// 计算本月对比数据(简化处理,实际应该查询上个月同期数据)
$currentMonthStart = strtotime(date('Y-m-01 00:00:00'));
$lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month')));
$lastMonthEnd = $currentMonthStart - 1;
// 获取本月统计数据(避免递归调用)
$currentMonthWhere = [
['wgpi.createTime', '>=', $currentMonthStart]
];
// 复制 workbenchId 条件
foreach ($where as $condition) {
if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') {
$currentMonthWhere[] = $condition;
break;
}
}
if ($contentIds !== null) {
$currentMonthWhere[] = ['wgpi.contentId', 'in', $contentIds];
}
$currentMonthSend = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($currentMonthWhere)
->count();
// 获取本月配置
$currentMonthConfigQuery = WorkbenchGroupPush::alias('wgp')
->join('workbench w', 'w.id = wgp.workbenchId', 'left')
->where('w.type', self::TYPE_GROUP_PUSH)
->where('w.isDel', 0);
if (!empty($workbenchId)) {
$currentMonthConfigQuery->where('wgp.workbenchId', $workbenchId);
} else {
foreach ($where as $condition) {
if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') {
$currentMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]);
break;
}
}
}
$currentMonthConfigs = $currentMonthConfigQuery->select();
$currentMonthPlanned = 0;
if (!empty($currentMonthConfigs)) {
$currentMonthDays = ceil(($endTime - $currentMonthStart) / 86400);
foreach ($currentMonthConfigs as $currentMonthConfig) {
$currentMonthMaxPerDay = intval($currentMonthConfig->maxPerDay ?? 0);
$currentMonthTargetType = intval($currentMonthConfig->targetType ?? 1);
if ($currentMonthTargetType == 1) {
$currentMonthGroups = $this->decodeJsonArray($currentMonthConfig->groups ?? []);
$currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays * count($currentMonthGroups);
} else {
$currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays;
}
}
}
$currentMonthReachRate = $currentMonthPlanned > 0 ? round(($currentMonthSend / $currentMonthPlanned) * 100, 1) : 0;
// 获取上个月统计数据
$lastMonthWhere = [
['wgpi.createTime', '>=', $lastMonthStart],
['wgpi.createTime', '<=', $lastMonthEnd]
];
// 复制 workbenchId 条件
foreach ($where as $condition) {
if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') {
$lastMonthWhere[] = $condition;
break;
}
}
if ($contentIds !== null) {
$lastMonthWhere[] = ['wgpi.contentId', 'in', $contentIds];
}
$lastMonthSend = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($lastMonthWhere)
->count();
// 获取上个月配置
$lastMonthConfigQuery = WorkbenchGroupPush::alias('wgp')
->join('workbench w', 'w.id = wgp.workbenchId', 'left')
->where('w.type', self::TYPE_GROUP_PUSH)
->where('w.isDel', 0);
if (!empty($workbenchId)) {
$lastMonthConfigQuery->where('wgp.workbenchId', $workbenchId);
} else {
foreach ($where as $condition) {
if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') {
$lastMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]);
break;
}
}
}
$lastMonthConfigs = $lastMonthConfigQuery->select();
$lastMonthPlanned = 0;
if (!empty($lastMonthConfigs)) {
$lastMonthDays = ceil(($lastMonthEnd - $lastMonthStart) / 86400);
foreach ($lastMonthConfigs as $lastMonthConfig) {
$lastMonthMaxPerDay = intval($lastMonthConfig->maxPerDay ?? 0);
$lastMonthTargetType = intval($lastMonthConfig->targetType ?? 1);
if ($lastMonthTargetType == 1) {
$lastMonthGroups = $this->decodeJsonArray($lastMonthConfig->groups ?? []);
$lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays * count($lastMonthGroups);
} else {
$lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays;
}
}
}
$lastMonthReachRate = $lastMonthPlanned > 0 ? round(($lastMonthSend / $lastMonthPlanned) * 100, 1) : 0;
// 获取上个月的回复和点击统计(简化处理)
$lastMonthSentItems = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($lastMonthWhere)
->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId')
->select();
$lastMonthReplyStats = $this->calculateReplyStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd);
$lastMonthClickStats = $this->calculateClickStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd);
return [
'reachRate' => [
'value' => $reachRate,
'trend' => round($reachRate - $lastMonthReachRate, 1),
'unit' => '%',
'description' => '成功发送/计划发送'
],
'replyRate' => [
'value' => $replyStats['replyRate'],
'trend' => round($replyStats['replyRate'] - $lastMonthReplyStats['replyRate'], 1),
'unit' => '%',
'description' => '收到回复/成功发送'
],
'avgReplyTime' => [
'value' => $replyStats['avgReplyTime'],
'trend' => round($lastMonthReplyStats['avgReplyTime'] - $replyStats['avgReplyTime'], 0),
'unit' => '分钟',
'description' => '从发送到回复的平均时长'
],
'clickRate' => [
'value' => $clickStats['clickRate'],
'trend' => round($clickStats['clickRate'] - $lastMonthClickStats['clickRate'], 1),
'unit' => '%',
'description' => '点击链接/成功发送'
],
'plannedSend' => $plannedSend,
'successSend' => $successSend,
'replyCount' => $replyStats['replyCount'],
'clickCount' => $clickStats['clickCount']
];
}
/**
* 计算回复统计
*/
private function calculateReplyStats($sentItems, $targetType, $startTime, $endTime)
{
if (empty($sentItems)) {
return ['replyRate' => 0, 'avgReplyTime' => 0, 'replyCount' => 0];
}
$replyCount = 0;
$totalReplyTime = 0;
$replyTimes = [];
foreach ($sentItems as $item) {
$itemArray = is_array($item) ? $item : (array)$item;
$sendTime = $itemArray['createTime'] ?? 0;
$accountId = $itemArray['wechatAccountId'] ?? 0;
if ($targetType == 1) {
// 群推送:查找群内回复消息
$groupId = $itemArray['groupId'] ?? 0;
$group = Db::name('wechat_group')->where('id', $groupId)->find();
if ($group) {
$replyMsg = Db::table('s2_wechat_message')
->where('wechatChatroomId', $group['chatroomId'])
->where('wechatAccountId', $accountId)
->where('isSend', 0) // 接收的消息
->where('wechatTime', '>', $sendTime)
->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复
->order('wechatTime', 'asc')
->find();
if ($replyMsg) {
$replyCount++;
$replyTime = $replyMsg['wechatTime'] - $sendTime;
$replyTimes[] = $replyTime;
$totalReplyTime += $replyTime;
}
}
} else {
// 好友推送:查找好友回复消息
$friendId = $itemArray['friendId'] ?? 0;
$friend = Db::table('s2_wechat_friend')->where('id', $friendId)->find();
if ($friend) {
$replyMsg = Db::table('s2_wechat_message')
->where('wechatFriendId', $friendId)
->where('wechatAccountId', $accountId)
->where('isSend', 0) // 接收的消息
->where('wechatTime', '>', $sendTime)
->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复
->order('wechatTime', 'asc')
->find();
if ($replyMsg) {
$replyCount++;
$replyTime = $replyMsg['wechatTime'] - $sendTime;
$replyTimes[] = $replyTime;
$totalReplyTime += $replyTime;
}
}
}
}
$successSend = count($sentItems);
$replyRate = $successSend > 0 ? round(($replyCount / $successSend) * 100, 1) : 0;
$avgReplyTime = $replyCount > 0 ? round(($totalReplyTime / $replyCount) / 60, 0) : 0; // 转换为分钟
return [
'replyRate' => $replyRate,
'avgReplyTime' => $avgReplyTime,
'replyCount' => $replyCount
];
}
/**
* 计算链接点击统计
*/
private function calculateClickStats($sentItems, $targetType, $startTime, $endTime)
{
if (empty($sentItems)) {
return ['clickRate' => 0, 'clickCount' => 0];
}
$clickCount = 0;
$linkContentIds = [];
// 获取所有发送的内容ID
foreach ($sentItems as $item) {
$itemArray = is_array($item) ? $item : (array)$item;
$contentId = $itemArray['contentId'] ?? 0;
if ($contentId > 0) {
$linkContentIds[] = $contentId;
}
}
if (empty($linkContentIds)) {
return ['clickRate' => 0, 'clickCount' => 0];
}
// 查询包含链接的内容
$linkContents = Db::name('content_item')
->whereIn('id', array_unique($linkContentIds))
->where('contentType', 2) // 链接类型
->column('id');
// 统计发送了链接内容的记录数
$linkSendCount = 0;
foreach ($sentItems as $item) {
$itemArray = is_array($item) ? $item : (array)$item;
$contentId = $itemArray['contentId'] ?? 0;
if (in_array($contentId, $linkContents)) {
$linkSendCount++;
}
}
// 简化处理:假设点击率基于链接消息的发送(实际应该从点击追踪系统获取)
// 这里可以根据业务需求调整,比如通过消息中的链接点击事件统计
$clickCount = $linkSendCount; // 简化处理,实际需要真实的点击数据
$successSend = count($sentItems);
$clickRate = $successSend > 0 ? round(($clickCount / $successSend) * 100, 1) : 0;
return [
'clickRate' => $clickRate,
'clickCount' => $clickCount
];
}
/**
* 获取话术组对比数据
*/
private function getContentLibraryComparison($workbenchId, $where, $startTime, $endTime, $contentIds = null)
{
$queryWhere = $where;
if ($contentIds !== null) {
$queryWhere[] = ['wgpi.contentId', 'in', $contentIds];
}
$comparison = Db::name('workbench_group_push_item')
->alias('wgpi')
->join('content_item ci', 'ci.id = wgpi.contentId', 'left')
->join('content_library cl', 'cl.id = ci.libraryId', 'left')
->where($queryWhere)
->where('cl.id', '<>', null)
->field([
'cl.id as libraryId',
'cl.name as libraryName',
'COUNT(DISTINCT wgpi.id) as pushCount'
])
->group('cl.id, cl.name')
->select();
$result = [];
foreach ($comparison as $item) {
$libraryId = $item['libraryId'];
$pushCount = intval($item['pushCount']);
// 获取该内容库的详细统计
$libraryContentIds = Db::name('content_item')
->where('libraryId', $libraryId)
->column('id');
if (empty($libraryContentIds)) {
$libraryContentIds = [-1];
}
$libraryWhere = array_merge($where, [['wgpi.contentId', 'in', $libraryContentIds]]);
$librarySentItems = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($libraryWhere)
->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId')
->select();
$config = WorkbenchGroupPush::where('workbenchId', $workbenchId)->find();
$targetType = $config ? intval($config->targetType) : 1;
$replyStats = $this->calculateReplyStats($librarySentItems, $targetType, $startTime, $endTime);
$clickStats = $this->calculateClickStats($librarySentItems, $targetType, $startTime, $endTime);
// 计算转化率(简化处理,实际需要根据业务定义)
$conversionRate = $pushCount > 0 ? round(($replyStats['replyCount'] / $pushCount) * 100, 1) : 0;
$result[] = [
'libraryId' => $libraryId,
'libraryName' => $item['libraryName'],
'pushCount' => $pushCount,
'reachRate' => 100, // 简化处理,实际应该计算
'replyRate' => $replyStats['replyRate'],
'clickRate' => $clickStats['clickRate'],
'conversionRate' => $conversionRate,
'avgReplyTime' => $replyStats['avgReplyTime'],
'level' => $this->getPerformanceLevel($replyStats['replyRate'], $clickStats['clickRate'], $conversionRate)
];
}
// 按回复率排序
usort($result, function($a, $b) {
return $b['replyRate'] <=> $a['replyRate'];
});
return $result;
}
/**
* 获取性能等级
*/
private function getPerformanceLevel($replyRate, $clickRate, $conversionRate)
{
$score = ($replyRate * 0.4) + ($clickRate * 0.3) + ($conversionRate * 0.3);
if ($score >= 40) {
return '优秀';
} elseif ($score >= 25) {
return '良好';
} elseif ($score >= 15) {
return '一般';
} else {
return '待提升';
}
}
/**
* 获取时段分析数据
*/
private function getTimePeriodAnalysis($workbenchId, $where, $startTime, $endTime, $contentIds = null)
{
$queryWhere = $where;
if ($contentIds !== null) {
$queryWhere[] = ['wgpi.contentId', 'in', $contentIds];
}
$analysis = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($queryWhere)
->field([
'FROM_UNIXTIME(wgpi.createTime, "%H") as hour',
'COUNT(*) as count'
])
->group('hour')
->order('hour', 'asc')
->select();
$result = [];
foreach ($analysis as $item) {
$result[] = [
'hour' => intval($item['hour']),
'count' => intval($item['count'])
];
}
return $result;
}
/**
* 获取互动深度数据
*/
private function getInteractionDepth($workbenchId, $where, $startTime, $endTime, $contentIds = null)
{
// 简化处理,实际需要更复杂的统计逻辑
return [
'singleReply' => 0, // 单次回复
'multipleReply' => 0, // 多次回复
'deepInteraction' => 0 // 深度互动
];
}
/**
* 获取推送历史记录列表
* @return \think\response\Json
*/
public function getGroupPushHistory()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$workbenchId = $this->request->param('workbenchId', 0);
$keyword = $this->request->param('keyword', '');
$pushType = $this->request->param('pushType', ''); // 推送类型筛选:''=全部, 'friend'=好友消息, 'group'=群消息, 'announcement'=群公告
$status = $this->request->param('status', ''); // 状态筛选:''=全部, 'success'=已完成, 'progress'=进行中, 'failed'=失败
$userId = $this->request->userInfo['id'];
// 构建工作台查询条件
$workbenchWhere = [
['w.userId', '=', $userId],
['w.type', '=', self::TYPE_GROUP_PUSH],
['w.isDel', '=', 0]
];
// 如果指定了工作台ID则验证权限并限制查询范围
if (!empty($workbenchId)) {
$workbench = Workbench::where([
['id', '=', $workbenchId],
['userId', '=', $userId],
['type', '=', self::TYPE_GROUP_PUSH],
['isDel', '=', 0]
])->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在']);
}
$workbenchWhere[] = ['w.id', '=', $workbenchId];
}
// 1. 先查询所有已执行的推送记录(按推送时间分组)
$pushHistoryQuery = Db::name('workbench_group_push_item')
->alias('wgpi')
->join('workbench w', 'w.id = wgpi.workbenchId', 'left')
->join('workbench_group_push wgp', 'wgp.workbenchId = wgpi.workbenchId', 'left')
->join('content_item ci', 'ci.id = wgpi.contentId', 'left')
->join('content_library cl', 'cl.id = ci.libraryId', 'left')
->where($workbenchWhere)
->field([
'wgpi.workbenchId',
'w.name as workbenchName',
'wgpi.contentId',
'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime',
'wgpi.targetType',
'wgp.groupPushSubType',
'MIN(wgpi.createTime) as createTime',
'COUNT(DISTINCT wgpi.id) as totalCount',
'cl.name as contentLibraryName'
])
->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType, wgp.groupPushSubType');
if (!empty($keyword)) {
$pushHistoryQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%');
}
$pushHistoryList = $pushHistoryQuery->order('createTime', 'desc')->select();
// 2. 查询所有任务(包括未执行的)
$allTasksQuery = Db::name('workbench')
->alias('w')
->join('workbench_group_push wgp', 'wgp.workbenchId = w.id', 'left')
->where($workbenchWhere)
->field([
'w.id as workbenchId',
'w.name as workbenchName',
'w.createTime',
'wgp.targetType',
'wgp.groupPushSubType',
'wgp.groups',
'wgp.friends',
'wgp.trafficPools'
]);
if (!empty($keyword)) {
$allTasksQuery->where('w.name', 'like', '%' . $keyword . '%');
}
$allTasks = $allTasksQuery->select();
// 3. 合并数据:已执行的推送记录 + 未执行的任务
$resultList = [];
$executedWorkbenchIds = [];
// 处理已执行的推送记录
foreach ($pushHistoryList as $item) {
$itemWorkbenchId = $item['workbenchId'];
$contentId = $item['contentId'];
$pushTime = $item['pushTime'];
$targetType = intval($item['targetType']);
$groupPushSubType = isset($item['groupPushSubType']) ? intval($item['groupPushSubType']) : 1;
// 标记该工作台已有执行记录
if (!in_array($itemWorkbenchId, $executedWorkbenchIds)) {
$executedWorkbenchIds[] = $itemWorkbenchId;
}
// 将时间字符串转换为时间戳范围(小时级别)
$pushTimeStart = strtotime($pushTime);
$pushTimeEnd = $pushTimeStart + 3600; // 一小时后
// 获取该次推送的详细统计
$pushWhere = [
['wgpi.workbenchId', '=', $itemWorkbenchId],
['wgpi.contentId', '=', $contentId],
['wgpi.createTime', '>=', $pushTimeStart],
['wgpi.createTime', '<', $pushTimeEnd],
['wgpi.targetType', '=', $targetType]
];
// 目标数量
if ($targetType == 1) {
// 群推送:统计群数量
$targetCount = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($pushWhere)
->where('wgpi.groupId', '<>', null)
->distinct(true)
->count('wgpi.groupId');
} else {
// 好友推送:统计好友数量
$targetCount = Db::name('workbench_group_push_item')
->alias('wgpi')
->where($pushWhere)
->where('wgpi.friendId', '<>', null)
->distinct(true)
->count('wgpi.friendId');
}
// 成功数和失败数(简化处理,实际需要根据发送状态判断)
$successCount = intval($item['totalCount']); // 简化处理
$failCount = 0; // 简化处理,实际需要从发送状态获取
// 状态判断
$itemStatus = $successCount > 0 ? 'success' : 'failed';
if ($failCount > 0 && $successCount > 0) {
$itemStatus = 'partial';
}
// 推送类型判断
$pushTypeText = '';
$pushTypeCode = '';
if ($targetType == 1) {
// 群推送
if ($groupPushSubType == 2) {
$pushTypeText = '群公告';
$pushTypeCode = 'announcement';
} else {
$pushTypeText = '群消息';
$pushTypeCode = 'group';
}
} else {
// 好友推送
$pushTypeText = '好友消息';
$pushTypeCode = 'friend';
}
$resultList[] = [
'workbenchId' => $itemWorkbenchId,
'taskName' => $item['workbenchName'] ?? '',
'pushType' => $pushTypeText,
'pushTypeCode' => $pushTypeCode,
'targetCount' => $targetCount,
'successCount' => $successCount,
'failCount' => $failCount,
'status' => $itemStatus,
'statusText' => $this->getStatusText($itemStatus),
'createTime' => date('Y-m-d H:i:s', $item['createTime']),
'contentLibraryName' => $item['contentLibraryName'] ?? ''
];
}
// 处理未执行的任务
foreach ($allTasks as $task) {
$taskWorkbenchId = $task['workbenchId'];
// 如果该任务已有执行记录,跳过(避免重复)
if (in_array($taskWorkbenchId, $executedWorkbenchIds)) {
continue;
}
$targetType = isset($task['targetType']) ? intval($task['targetType']) : 1;
$groupPushSubType = isset($task['groupPushSubType']) ? intval($task['groupPushSubType']) : 1;
// 计算目标数量(从配置中获取)
$targetCount = 0;
if ($targetType == 1) {
// 群推送:统计配置的群数量
$groups = json_decode($task['groups'] ?? '[]', true);
$targetCount = is_array($groups) ? count($groups) : 0;
} else {
// 好友推送:统计配置的好友数量或流量池数量
$friends = json_decode($task['friends'] ?? '[]', true);
$trafficPools = json_decode($task['trafficPools'] ?? '[]', true);
$friendCount = is_array($friends) ? count($friends) : 0;
$poolCount = is_array($trafficPools) ? count($trafficPools) : 0;
// 如果配置了流量池,目标数量暂时显示为流量池数量(实际数量需要从流量池中统计)
$targetCount = $friendCount > 0 ? $friendCount : $poolCount;
}
// 推送类型判断
$pushTypeText = '';
$pushTypeCode = '';
if ($targetType == 1) {
// 群推送
if ($groupPushSubType == 2) {
$pushTypeText = '群公告';
$pushTypeCode = 'announcement';
} else {
$pushTypeText = '群消息';
$pushTypeCode = 'group';
}
} else {
// 好友推送
$pushTypeText = '好友消息';
$pushTypeCode = 'friend';
}
$resultList[] = [
'workbenchId' => $taskWorkbenchId,
'taskName' => $task['workbenchName'] ?? '',
'pushType' => $pushTypeText,
'pushTypeCode' => $pushTypeCode,
'targetCount' => $targetCount,
'successCount' => 0,
'failCount' => 0,
'status' => 'pending',
'statusText' => '进行中',
'createTime' => date('Y-m-d H:i:s', $task['createTime']),
'contentLibraryName' => ''
];
}
// 应用筛选条件
$filteredList = [];
foreach ($resultList as $item) {
// 推送类型筛选
if (!empty($pushType)) {
if ($pushType === 'friend' && $item['pushTypeCode'] !== 'friend') {
continue;
}
if ($pushType === 'group' && $item['pushTypeCode'] !== 'group') {
continue;
}
if ($pushType === 'announcement' && $item['pushTypeCode'] !== 'announcement') {
continue;
}
}
// 状态筛选
if (!empty($status)) {
if ($status === 'success' && $item['status'] !== 'success') {
continue;
}
if ($status === 'progress') {
// 进行中:包括 partial 和 pending
if ($item['status'] !== 'partial' && $item['status'] !== 'pending') {
continue;
}
}
if ($status === 'failed' && $item['status'] !== 'failed') {
continue;
}
}
$filteredList[] = $item;
}
// 按创建时间倒序排序
usort($filteredList, function($a, $b) {
return strtotime($b['createTime']) - strtotime($a['createTime']);
});
// 分页处理
$total = count($filteredList);
$offset = ($page - 1) * $limit;
$list = array_slice($filteredList, $offset, $limit);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $list,
'total' => $total,
'page' => $page,
'limit' => $limit
]
]);
}
/**
* 获取状态文本
* @param string $status 状态码
* @return string 状态文本
*/
private function getStatusText($status)
{
$statusMap = [
'success' => '已完成',
'partial' => '进行中',
'pending' => '进行中',
'failed' => '失败'
];
return $statusMap[$status] ?? '未知';
}
/**
* 规范化客服微信ID列表
* @param mixed $ownerWechatIds
* @param WorkbenchGroupPush|null $originalConfig
* @param array $deviceGroups 设备ID数组
* @return array
* @throws \Exception
*/
private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null, array $deviceGroups = []): array
{
// 处理设备ID数组
$deviceGroupsList = $this->extractIdList($deviceGroups, '设备参数格式错误');
if ($ownerWechatIds === null) {
$existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : [];
// 如果原有配置为空且没有传设备ID则报错
if (empty($existing) && empty($deviceGroupsList)) {
throw new \Exception('请至少选择一个客服微信或设备');
}
// 如果原有配置为空但有设备ID返回空数组允许使用设备ID
if (empty($existing) && !empty($deviceGroupsList)) {
return [];
}
return $existing;
}
if (!is_array($ownerWechatIds)) {
throw new \Exception('客服参数格式错误');
}
$normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误');
// 如果 ownerWechatIds 为空但传了设备ID则允许两个传一个即可
if (empty($normalized) && empty($deviceGroupsList)) {
throw new \Exception('请至少选择一个客服微信或设备');
}
return $normalized;
}
/**
* 构建群推送配置数据
* @param array $param
* @param array $ownerWechatIds
* @param WorkbenchGroupPush|null $originalConfig
* @return array
* @throws \Exception
*/
private function prepareGroupPushData(array $param, array $ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array
{
$targetTypeDefault = $originalConfig ? intval($originalConfig->targetType) : 1;
$targetType = intval($this->getParamValue($param, 'targetType', $targetTypeDefault)) ?: 1;
$groupPushSubTypeDefault = $originalConfig ? intval($originalConfig->groupPushSubType) : 1;
$groupPushSubType = intval($this->getParamValue($param, 'groupPushSubType', $groupPushSubTypeDefault)) ?: 1;
if (!in_array($groupPushSubType, [1, 2], true)) {
$groupPushSubType = 1;
}
$data = [
'pushType' => $this->toBoolInt($this->getParamValue($param, 'pushType', $originalConfig->pushType ?? 0)),
'targetType' => $targetType,
'startTime' => $this->getParamValue($param, 'startTime', $originalConfig->startTime ?? ''),
'endTime' => $this->getParamValue($param, 'endTime', $originalConfig->endTime ?? ''),
'maxPerDay' => intval($this->getParamValue($param, 'maxPerDay', $originalConfig->maxPerDay ?? 0)),
'pushOrder' => $this->getParamValue($param, 'pushOrder', $originalConfig->pushOrder ?? 1),
'groupPushSubType' => $groupPushSubType,
'status' => $this->toBoolInt($this->getParamValue($param, 'status', $originalConfig->status ?? 0)),
'socialMediaId' => $this->getParamValue($param, 'socialMediaId', $originalConfig->socialMediaId ?? ''),
'promotionSiteId' => $this->getParamValue($param, 'promotionSiteId', $originalConfig->promotionSiteId ?? ''),
'friendIntervalMin' => intval($this->getParamValue($param, 'friendIntervalMin', $originalConfig->friendIntervalMin ?? 10)),
'friendIntervalMax' => intval($this->getParamValue($param, 'friendIntervalMax', $originalConfig->friendIntervalMax ?? 20)),
'messageIntervalMin' => intval($this->getParamValue($param, 'messageIntervalMin', $originalConfig->messageIntervalMin ?? 1)),
'messageIntervalMax' => intval($this->getParamValue($param, 'messageIntervalMax', $originalConfig->messageIntervalMax ?? 12)),
'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)),
'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE),
];
// 处理设备ID数组deviceGroups保存到 devices 字段
$deviceGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->devices ?? []) : [];
$deviceGroupsParam = $this->getParamValue($param, 'deviceGroups', null);
$deviceGroups = $deviceGroupsParam !== null
? $this->extractIdList($deviceGroupsParam, '设备参数格式错误')
: $deviceGroupsExisting;
$data['devices'] = json_encode($deviceGroups, JSON_UNESCAPED_UNICODE);
if ($data['friendIntervalMin'] > $data['friendIntervalMax']) {
throw new \Exception('目标间最小间隔不能大于最大间隔');
}
if ($data['messageIntervalMin'] > $data['messageIntervalMax']) {
throw new \Exception('消息间最小间隔不能大于最大间隔');
}
// 群公告groupPushSubType = 2contentGroups 可以为空,不需要验证
if ($targetType === 1 && $groupPushSubType === 2) {
// 群公告可以不传内容库,允许为空,不进行任何验证
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
if ($contentGroupsParam !== null) {
// 如果传入了参数,尝试解析,但不验证格式和是否为空
try {
$contentGroups = $this->extractIdList($contentGroupsParam, '内容库参数格式错误');
} catch (\Exception $e) {
// 群公告时忽略格式错误,使用空数组
$contentGroups = [];
}
} else {
// 如果没有传入参数,使用现有配置或空数组
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
$contentGroups = $contentGroupsExisting;
}
} else {
// 其他情况,正常处理并验证
$contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : [];
$contentGroupsParam = $this->getParamValue($param, 'contentGroups', null);
$contentGroups = $contentGroupsParam !== null
? $this->extractIdList($contentGroupsParam, '内容库参数格式错误')
: $contentGroupsExisting;
// 其他情况,内容库为必填
if (empty($contentGroups)) {
throw new \Exception('请至少选择一个内容库');
}
}
$data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE);
$postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : [];
$postPushTagsParam = $this->getParamValue($param, 'postPushTags', null);
$postPushTags = $postPushTagsParam !== null
? $this->extractIdList($postPushTagsParam, '推送标签参数格式错误')
: $postPushTagsExisting;
$data['postPushTags'] = json_encode($postPushTags, JSON_UNESCAPED_UNICODE);
if ($targetType === 1) {
$data['isLoop'] = $this->toBoolInt($this->getParamValue($param, 'isLoop', $originalConfig->isLoop ?? 0));
$groupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->groups ?? []) : [];
$wechatGroups = array_key_exists('wechatGroups', $param)
? $this->extractIdList($param['wechatGroups'], '群参数格式错误')
: $groupsExisting;
if (empty($wechatGroups)) {
throw new \Exception('群推送必须选择微信群');
}
$data['groups'] = json_encode($wechatGroups, JSON_UNESCAPED_UNICODE);
$data['friends'] = json_encode([], JSON_UNESCAPED_UNICODE);
$data['trafficPools'] = json_encode([], JSON_UNESCAPED_UNICODE);
if ($groupPushSubType === 2) {
$announcementContent = $this->getParamValue($param, 'announcementContent', $originalConfig->announcementContent ?? '');
if (empty($announcementContent)) {
throw new \Exception('群公告必须输入公告内容');
}
$enableAiRewrite = $this->toBoolInt($this->getParamValue($param, 'enableAiRewrite', $originalConfig->enableAiRewrite ?? 0));
$aiRewritePrompt = trim((string)$this->getParamValue($param, 'aiRewritePrompt', $originalConfig->aiRewritePrompt ?? ''));
if ($enableAiRewrite === 1 && $aiRewritePrompt === '') {
throw new \Exception('启用AI智能话术改写时必须输入改写提示词');
}
$data['announcementContent'] = $announcementContent;
$data['enableAiRewrite'] = $enableAiRewrite;
$data['aiRewritePrompt'] = $aiRewritePrompt;
} else {
$data['groupPushSubType'] = 1;
$data['announcementContent'] = '';
$data['enableAiRewrite'] = 0;
$data['aiRewritePrompt'] = '';
}
} else {
$data['isLoop'] = 0;
$friendsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->friends ?? []) : [];
$trafficPoolsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->trafficPools ?? []) : [];
$friendTargets = array_key_exists('wechatFriends', $param)
? $this->extractIdList($param['wechatFriends'], '好友参数格式错误')
: $friendsExisting;
$trafficPools = array_key_exists('trafficPools', $param)
? $this->extractIdList($param['trafficPools'], '流量池参数格式错误')
: $trafficPoolsExisting;
if (empty($friendTargets) && empty($trafficPools)) {
throw new \Exception('好友推送需至少选择好友或流量池');
}
$data['friends'] = json_encode($friendTargets, JSON_UNESCAPED_UNICODE);
$data['trafficPools'] = json_encode($trafficPools, JSON_UNESCAPED_UNICODE);
$data['groups'] = json_encode([], JSON_UNESCAPED_UNICODE);
$data['groupPushSubType'] = 1;
$data['announcementContent'] = '';
$data['enableAiRewrite'] = 0;
$data['aiRewritePrompt'] = '';
}
return $data;
}
/**
* 获取参数值,若不存在则返回默认值
* @param array $param
* @param string $key
* @param mixed $default
* @return mixed
*/
private function getParamValue(array $param, string $key, $default)
{
return array_key_exists($key, $param) ? $param[$key] : $default;
}
/**
* 将值转换为整型布尔
* @param mixed $value
* @return int
*/
private function toBoolInt($value): int
{
return empty($value) ? 0 : 1;
}
/**
* 从参数中提取ID列表
* @param mixed $items
* @param string $errorMessage
* @return array
* @throws \Exception
*/
private function extractIdList($items, string $errorMessage = '参数格式错误'): array
{
if (!is_array($items)) {
throw new \Exception($errorMessage);
}
$ids = [];
foreach ($items as $item) {
if (is_array($item) && isset($item['id'])) {
$item = $item['id'];
}
if ($item === '' || $item === null) {
continue;
}
$ids[] = $item;
}
return array_values(array_unique($ids));
}
/**
* 解码JSON数组
* @param mixed $value
* @return array
*/
private function decodeJsonArray($value): array
{
if (empty($value)) {
return [];
}
if (is_array($value)) {
return $value;
}
$decoded = json_decode($value, true);
return is_array($decoded) ? $decoded : [];
}
/**
* 验证内容是否包含链接
* @param string $content 要检测的内容
* @return bool
*/
private function containsLink($content)
{
// 定义各种链接的正则表达式模式
$patterns = [
// HTTP/HTTPS链接
'/https?:\/\/[^\s]+/i',
// 京东商品链接
'/item\.jd\.com\/\d+/i',
// 京东短链接
'/u\.jd\.com\/[a-zA-Z0-9]+/i',
// 淘宝商品链接
'/item\.taobao\.com\/item\.htm\?id=\d+/i',
// 天猫商品链接
'/detail\.tmall\.com\/item\.htm\?id=\d+/i',
// 淘宝短链接
'/m\.tb\.cn\/[a-zA-Z0-9]+/i',
// 拼多多链接
'/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i',
// 苏宁易购链接
'/product\.suning\.com\/\d+\/\d+\.html/i',
// 通用域名模式(包含常见电商域名)
'/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i',
// 通用短链接模式
'/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i'
];
// 遍历所有模式进行匹配
foreach ($patterns as $pattern) {
if (preg_match($pattern, $content)) {
return true;
}
}
return false;
}
}
if (empty($workbenchId)) {
return json(['code' => 400, 'msg' => '工作台ID不能为空']);
}
// 验证工作台权限
$where = [
['id', '=', $workbenchId],
['companyId', '=', $this->request->userInfo['companyId']],
['type', '=', self::TYPE_GROUP_CREATE],
['isDel', '=', 0]
];
if (empty($this->request->userInfo['isAdmin'])) {
$where[] = ['userId', '=', $this->request->userInfo['id']];
}
$workbench = Workbench::where($where)->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在或无权限']);
}
// 获取已创建的群ID列表状态为成功且groupId不为空
$groupIds = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('status', 2) // STATUS_SUCCESS = 2
->where('groupId', '<>', null)
->group('groupId')
->column('groupId');
if (empty($groupIds)) {
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => [],
'total' => 0,
'page' => $page,
'limit' => $limit
]
]);
}
// 查询群组详细信息从s2_wechat_chatroom表查询
$query = Db::table('s2_wechat_chatroom')->alias('wc')
->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left')
->where('wc.id', 'in', $groupIds)
->where('wc.isDeleted', 0);
// 关键字搜索
if (!empty($keyword)) {
$query->where(function ($q) use ($keyword) {
$q->where('wc.nickname', 'like', '%' . $keyword . '%')
->whereOr('wc.chatroomId', 'like', '%' . $keyword . '%')
->whereOr('wa.nickName', 'like', '%' . $keyword . '%');
});
}
$total = $query->count();
$list = $query->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias')
->order('wc.createTime', 'desc')
->page($page, $limit)
->select();
// 统计每个群的成员数量和成员信息
foreach ($list as &$item) {
// 统计该群的成员数量从workbench_group_create_item表统计
$memberCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('groupId', $item['id'])
->where('status', 'in', [2, 4]) // 创建成功和管理员好友已拉入
->count();
// 获取成员列表(用于显示成员头像)
$memberList = Db::name('workbench_group_create_item')->alias('wgci')
->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left')
->where('wgci.workbenchId', $workbenchId)
->where('wgci.groupId', $item['id'])
->where('wgci.status', 'in', [2, 4])
->field('wf.avatar, wf.wechatId, wf.nickname')
->order('wgci.createTime', 'asc')
->limit(10) // 最多显示10个成员头像
->select();
// 格式化成员头像列表
$memberAvatars = [];
foreach ($memberList as $member) {
if (!empty($member['avatar'])) {
$memberAvatars[] = [
'avatar' => $member['avatar'],
'wechatId' => $member['wechatId'] ?? '',
'nickname' => $member['nickname'] ?? ''
];
}
}
// 计算剩余成员数(用于显示"+XX"
$remainingCount = $memberCount > count($memberAvatars) ? $memberCount - count($memberAvatars) : 0;
// 格式化返回数据
$item['memberCount'] = $memberCount;
$item['memberCountText'] = $memberCount . '人'; // 格式化为"XX人"
$item['createTime'] = !empty($item['createTime']) ? date('Y-m-d', $item['createTime']) : ''; // 格式化为"YYYY-MM-DD"
$item['memberAvatars'] = $memberAvatars; // 成员头像列表最多10个
$item['remainingCount'] = $remainingCount; // 剩余成员数(用于显示"+XX"
// 保留原有字段,但调整格式
$item['groupName'] = $item['groupName'] ?? '';
$item['groupAvatar'] = $item['groupAvatar'] ?? '';
}
unset($item);
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $list,
'total' => $total,
'page' => $page,
'limit' => $limit
]
]);
}
}
{
$workbenchId = $this->request->param('workbenchId', 0);
$groupId = $this->request->param('groupId', 0);
if (empty($workbenchId)) {
return json(['code' => 400, 'msg' => '工作台ID不能为空']);
}
if (empty($groupId)) {
return json(['code' => 400, 'msg' => '群ID不能为空']);
}
// 验证工作台权限
$where = [
['id', '=', $workbenchId],
['companyId', '=', $this->request->userInfo['companyId']],
['type', '=', self::TYPE_GROUP_CREATE],
['isDel', '=', 0]
];
if (empty($this->request->userInfo['isAdmin'])) {
$where[] = ['userId', '=', $this->request->userInfo['id']];
}
$workbench = Workbench::where($where)->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在或无权限']);
}
// 验证该群是否属于该工作台
$groupItem = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('groupId', $groupId)
->where('status', 2) // STATUS_SUCCESS = 2
->find();
if (empty($groupItem)) {
return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']);
}
// 查询群基本信息从s2_wechat_chatroom表查询
$group = Db::table('s2_wechat_chatroom')->alias('wc')
->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left')
->where('wc.id', $groupId)
->where('wc.isDeleted', 0)
->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias,wc.announce')
->find();
if (empty($group)) {
return json(['code' => 404, 'msg' => '群不存在']);
}
// 获取chatroomId
$chatroomId = $group['chatroomId'] ?? '';
if (empty($chatroomId)) {
return json(['code' => 400, 'msg' => '群聊ID不存在']);
}
// 从s2_wechat_chatroom_member表查询所有成员列表不限制数量
$memberList = Db::table('s2_wechat_chatroom_member')->alias('wcm')
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = wcm.wechatId', 'left')
->where('wcm.chatroomId', $chatroomId)
->field('wcm.wechatId,wcm.nickname as memberNickname,wcm.avatar as memberAvatar,wcm.conRemark as memberRemark,wcm.alias as memberAlias,wcm.createTime as joinTime,wcm.updateTime,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar')
->order('wcm.createTime', 'asc')
->select();
// 去重按wechatId去重保留第一条记录
$memberMap = [];
foreach ($memberList as $member) {
$wechatId = $member['wechatId'] ?? '';
if (!empty($wechatId) && !isset($memberMap[$wechatId])) {
$memberMap[$wechatId] = $member;
}
}
$memberList = array_values($memberMap); // 重新索引数组
// 获取在群中的成员wechatId列表用于判断是否已退群
$inGroupWechatIds = array_column($memberList, 'wechatId');
$inGroupWechatIds = array_filter($inGroupWechatIds); // 过滤空值
// 获取通过自动建群加入的成员信息(用于判断入群状态和已退群成员)
$autoJoinMemberList = Db::name('workbench_group_create_item')
->alias('wgci')
->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left')
->where('wgci.workbenchId', $workbenchId)
->where('wgci.groupId', $groupId)
->where('wgci.status', 'in', [2, 4]) // 创建成功和管理员好友已拉入
->field('wf.wechatId,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar,wgci.createTime as autoJoinTime')
->select();
// 去重按wechatId去重保留第一条记录
$autoJoinMemberMap = [];
foreach ($autoJoinMemberList as $autoMember) {
$wechatId = $autoMember['wechatId'] ?? '';
if (!empty($wechatId) && !isset($autoJoinMemberMap[$wechatId])) {
$autoJoinMemberMap[$wechatId] = $autoMember;
}
}
$autoJoinMemberList = array_values($autoJoinMemberMap); // 重新索引数组
// 统计成员总数(包括在群中的成员和已退群的成员)
// 先统计在群中的成员数
$inGroupCount = count($memberList);
// 统计已退群的成员数(在自动建群记录中但不在群中的成员)
$quitCount = 0;
if (!empty($autoJoinMemberList)) {
foreach ($autoJoinMemberList as $autoMember) {
$wechatId = $autoMember['wechatId'] ?? '';
if (!empty($wechatId) && !in_array($wechatId, $inGroupWechatIds)) {
$quitCount++;
}
}
}
// 总成员数 = 在群中的成员数 + 已退群的成员数
$memberCount = $inGroupCount + $quitCount;
// 获取自动建群加入的成员wechatId列表用于判断入群状态
$autoJoinWechatIds = array_column($autoJoinMemberList, 'wechatId');
$autoJoinWechatIds = array_filter($autoJoinWechatIds); // 过滤空值
// 格式化在群中的成员列表
$members = [];
$addedWechatIds = []; // 用于记录已添加的成员wechatId避免重复
$ownerWechatId = $group['ownerWechatId'] ?? '';
foreach ($memberList as $member) {
$wechatId = $member['wechatId'] ?? '';
// 跳过空wechatId和已添加的成员
if (empty($wechatId) || in_array($wechatId, $addedWechatIds)) {
continue;
}
// 标记为已添加
$addedWechatIds[] = $wechatId;
// 根据wechatId判断是否为群主
$isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0;
// 判断入群状态:如果在自动建群记录中,说明是通过自动建群加入的;否则是其他方式加入的
$joinStatus = in_array($wechatId, $autoJoinWechatIds) ? 'auto' : 'manual';
// 判断是否已退群如果成员在s2_wechat_chatroom_member表中存在说明在群中否则已退群
// 由于我们已经从s2_wechat_chatroom_member表查询所以这里都是"在群中"状态
$isQuit = 0; // 0=在群中1=已退群
// 优先使用friend表的昵称和头像如果没有则使用member表的
$nickname = !empty($member['friendNickname']) ? $member['friendNickname'] : ($member['memberNickname'] ?? '');
$avatar = !empty($member['friendAvatar']) ? $member['friendAvatar'] : ($member['memberAvatar'] ?? '');
$members[] = [
'friendId' => $member['friendId'] ?? 0,
'wechatId' => $wechatId,
'nickname' => $nickname,
'avatar' => $avatar,
'alias' => $member['memberAlias'] ?? '',
'remark' => $member['memberRemark'] ?? '',
'isOwner' => $isOwner, // 标记群主
'joinStatus' => $joinStatus, // 入群状态auto=自动建群加入manual=其他方式加入
'isQuit' => $isQuit, // 是否已退群0=在群中1=已退群
'joinTime' => !empty($member['joinTime']) ? date('Y-m-d H:i:s', $member['joinTime']) : '', // 入群时间
];
}
// 添加已退群的成员(在自动建群记录中但不在群中的成员)
foreach ($autoJoinMemberList as $autoMember) {
$wechatId = $autoMember['wechatId'] ?? '';
// 跳过空wechatId、已在群中的成员和已添加的成员
if (empty($wechatId) || in_array($wechatId, $inGroupWechatIds) || in_array($wechatId, $addedWechatIds)) {
continue;
}
// 标记为已添加
$addedWechatIds[] = $wechatId;
// 根据wechatId判断是否为群主
$isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0;
$members[] = [
'friendId' => $autoMember['friendId'] ?? 0,
'wechatId' => $wechatId,
'nickname' => $autoMember['friendNickname'] ?? '',
'avatar' => $autoMember['friendAvatar'] ?? '',
'alias' => '',
'remark' => '',
'isOwner' => $isOwner, // 标记群主
'joinStatus' => 'auto', // 入群状态auto=自动建群加入
'isQuit' => 1, // 是否已退群1=已退群
'joinTime' => !empty($autoMember['autoJoinTime']) ? date('Y-m-d H:i:s', $autoMember['autoJoinTime']) : '', // 入群时间
];
}
// 将群主排在第一位
usort($members, function($a, $b) {
if ($a['isOwner'] == $b['isOwner']) {
return 0;
}
return $a['isOwner'] > $b['isOwner'] ? -1 : 1;
});
// 格式化返回数据
$result = [
'id' => $group['id'],
'groupName' => $group['groupName'] ?? '',
'chatroomId' => $group['chatroomId'] ?? '',
'groupAvatar' => $group['groupAvatar'] ?? '',
'ownerWechatId' => $group['ownerWechatId'] ?? '',
'ownerNickname' => $group['ownerNickname'] ?? '',
'ownerAvatar' => $group['ownerAvatar'] ?? '',
'ownerAlias' => $group['ownerAlias'] ?? '',
'announce' => $group['announce'] ?? '',
'createTime' => !empty($group['createTime']) ? date('Y-m-d H:i', $group['createTime']) : '', // 格式化为"YYYY-MM-DD HH:MM"
'memberCount' => $memberCount,
'memberCountText' => $memberCount . '人', // 格式化为"XX人"
'workbenchName' => $workbench->name ?? '', // 任务名称(工作台名称)
'members' => $members // 所有成员列表
];
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $result
]);
}
/**
* 同步群最新信息(包括群成员)
* @return \think\response\Json
*/
public function syncGroupInfo()
{
$workbenchId = $this->request->param('workbenchId', 0);
$groupId = $this->request->param('groupId', 0);
if (empty($workbenchId)) {
return json(['code' => 400, 'msg' => '工作台ID不能为空']);
}
if (empty($groupId)) {
return json(['code' => 400, 'msg' => '群ID不能为空']);
}
// 验证工作台权限
$where = [
['id', '=', $workbenchId],
['companyId', '=', $this->request->userInfo['companyId']],
['type', '=', self::TYPE_GROUP_CREATE],
['isDel', '=', 0]
];
if (empty($this->request->userInfo['isAdmin'])) {
$where[] = ['userId', '=', $this->request->userInfo['id']];
}
$workbench = Workbench::where($where)->find();
if (empty($workbench)) {
return json(['code' => 404, 'msg' => '工作台不存在或无权限']);
}
// 验证该群是否属于该工作台
$groupItem = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('groupId', $groupId)
->where('status', 2) // STATUS_SUCCESS = 2
->find();
if (empty($groupItem)) {
return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']);
}
// 查询群基本信息获取chatroomId和wechatAccountWechatId
$group = Db::table('s2_wechat_chatroom')
->where('id', $groupId)
->where('isDeleted', 0)
->field('id,chatroomId,wechatAccountWechatId')
->find();
if (empty($group)) {
return json(['code' => 404, 'msg' => '群不存在']);
}
$chatroomId = $group['chatroomId'] ?? '';
$wechatAccountWechatId = $group['wechatAccountWechatId'] ?? '';
if (empty($chatroomId)) {
return json(['code' => 400, 'msg' => '群聊ID不存在']);
}
try {
// 实例化WechatChatroomController
$chatroomController = new \app\api\controller\WechatChatroomController();
// 1. 同步群信息调用getlist方法
$syncData = [
'wechatChatroomId' => $chatroomId, // 使用chatroomId作为wechatChatroomId来指定要同步的群
'wechatAccountKeyword' => $wechatAccountWechatId, // 通过群主微信ID筛选
'isDeleted' => false,
'pageIndex' => 1,
'pageSize' => 100 // 获取足够多的数据
];
$syncResult = $chatroomController->getlist($syncData, true, 0); // isInner = true
$syncResponse = json_decode($syncResult, true);
if (empty($syncResponse['code']) || $syncResponse['code'] != 200) {
return json(['code' => 500, 'msg' => '同步群信息失败:' . ($syncResponse['msg'] ?? '未知错误')]);
}
// 2. 同步群成员信息调用listChatroomMember方法
// wechatChatroomId 使用 s2_wechat_chatroom 表的 id即groupId
// chatroomId 使用群聊IDchatroomId
$wechatChatroomId = $groupId; // s2_wechat_chatroom表的id
$memberSyncResult = $chatroomController->listChatroomMember($wechatChatroomId, $chatroomId, true); // isInner = true
$memberSyncResponse = json_decode($memberSyncResult, true);
if (empty($memberSyncResponse['code']) || $memberSyncResponse['code'] != 200) {
// 成员同步失败不影响整体结果,记录警告即可
\think\facade\Log::warning("同步群成员失败。群ID: {$groupId}, 群聊ID: {$chatroomId}, 错误: " . ($memberSyncResponse['msg'] ?? '未知错误'));
}
return json([
'code' => 200,
'msg' => '同步成功',
'data' => [
'groupId' => $groupId,
'chatroomId' => $chatroomId,
'groupInfoSynced' => true,
'memberInfoSynced' => !empty($memberSyncResponse['code']) && $memberSyncResponse['code'] == 200
]
]);
} catch (\Exception $e) {
\think\facade\Log::error("同步群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage());
return json(['code' => 500, 'msg' => '同步失败:' . $e->getMessage()]);
}
}
/**
* 修改群名称、群公告
* @return \think\response\Json
*/
public function modifyGroupInfo()
{
$workbenchId = $this->request->param('workbenchId', 0);
$groupId = $this->request->param('groupId', 0);
$chatroomName = $this->request->param('chatroomName', '');
$announce = $this->request->param('announce', '');
if (empty($workbenchId)) {
return json(['code' => 400, 'msg' => '工作台ID不能为空']);
}
if (empty($groupId)) {
return json(['code' => 400, 'msg' => '群ID不能为空']);
}
// 至少需要提供一个修改项
if (empty($chatroomName) && empty($announce)) {
return json(['code' => 400, 'msg' => '请至少提供群名称或群公告中的一个参数']);
}
// 查询群基本信息
$group = Db::table('s2_wechat_chatroom')
->where('id', $groupId)
->where('isDeleted', 0)
->field('id,chatroomId,wechatAccountWechatId,accountId,wechatAccountId')
->find();
if (empty($group)) {
return json(['code' => 404, 'msg' => '群不存在']);
}
$chatroomId = $group['id'] ?? '';
if (empty($chatroomId)) {
return json(['code' => 400, 'msg' => '群聊ID不存在']);
}
try {
// 直接使用群表中的账号信息
$executeAccountId = $group['accountId'] ?? 0;
$executeWechatAccountId = $group['wechatAccountId'] ?? 0;
$executeWechatId = $group['wechatAccountWechatId'] ?? '';
// 确保 wechatId 不为空
if (empty($executeWechatId)) {
return json(['code' => 400, 'msg' => '无法获取微信账号ID']);
}
// 调用 WebSocketController 修改群信息
// 获取系统API账号信息用于WebSocket连接
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
if (empty($username) || empty($password)) {
return json(['code' => 500, 'msg' => '系统API账号配置缺失']);
}
// 获取系统账号ID
$systemAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
if (empty($systemAccountId)) {
return json(['code' => 500, 'msg' => '未找到系统账号ID']);
}
$webSocketController = new WebSocketController([
'userName' => $username,
'password' => $password,
'accountId' => $systemAccountId
]);
// 构建修改参数
$modifyData = [
'wechatChatroomId' => $chatroomId,
'wechatAccountId' => $executeWechatAccountId,
];
if (!empty($chatroomName)) {
$modifyData['chatroomName'] = $chatroomName;
}
if (!empty($announce)) {
$modifyData['announce'] = $announce;
}
$modifyResult = $webSocketController->CmdChatroomModifyInfo($modifyData);
$modifyResponse = json_decode($modifyResult, true);
if (empty($modifyResponse['code']) || $modifyResponse['code'] != 200) {
return json(['code' => 500, 'msg' => '修改群信息失败:' . ($modifyResponse['msg'] ?? '未知错误')]);
}
// 修改成功后更新数据库
$updateData = [
'updateTime' => time()
];
// 如果修改了群名称,更新数据库
if (!empty($chatroomName)) {
$updateData['nickname'] = $chatroomName;
}
// 如果修改了群公告,更新数据库
if (!empty($announce)) {
$updateData['announce'] = $announce;
}
// 更新数据库
Db::table('s2_wechat_chatroom')
->where('id', $groupId)
->update($updateData);
return json([
'code' => 200,
'msg' => '修改成功',
'data' => [
'groupId' => $groupId,
'chatroomId' => $chatroomId,
'chatroomName' => $chatroomName,
'announce' => $announce
]
]);
} catch (\Exception $e) {
\think\facade\Log::error("修改群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage());
return json(['code' => 500, 'msg' => '修改失败:' . $e->getMessage()]);
}
}
/**
* 转移群聊到指定账号
* @param int $groupId 群IDs2_wechat_chatroom表的id
* @param string $chatroomId 群聊ID
* @param int $toAccountId 目标账号IDs2_company_account表的id
* @param int $toWechatAccountId 目标微信账号IDs2_wechat_account表的id
* @return array ['success' => bool, 'msg' => string]
*/
protected function transferChatroomToAccount($groupId, $chatroomId, $toAccountId, $toWechatAccountId)
{
try {
// 查询目标账号信息
$targetAccount = Db::table('s2_company_account')
->where('id', $toAccountId)
->field('id,userName,realName,nickname')
->find();
if (empty($targetAccount)) {
return ['success' => false, 'msg' => '目标账号不存在'];
}
// 查询目标微信账号信息
$targetWechatAccount = Db::table('s2_wechat_account')
->where('id', $toWechatAccountId)
->field('id,wechatId,deviceAccountId')
->find();
if (empty($targetWechatAccount)) {
return ['success' => false, 'msg' => '目标微信账号不存在'];
}
// 调用 AutomaticAssign 进行群聊转移
$automaticAssign = new \app\api\controller\AutomaticAssign();
// 构建转移参数(通过 API 调用)
$transferData = [
'wechatChatroomId' => $chatroomId, // 使用群聊ID
'toAccountId' => $toAccountId,
'wechatAccountKeyword' => $targetWechatAccount['wechatId'] ?? '',
'isDeleted' => false
];
// 直接更新数据库(因为 API 可能不支持指定单个群聊ID转移
// 更新 s2_wechat_chatroom 表的 accountId 和 wechatAccountId
Db::table('s2_wechat_chatroom')
->where('id', $groupId)
->update([
'accountId' => $toAccountId,
'accountUserName' => $targetAccount['userName'] ?? '',
'accountRealName' => $targetAccount['realName'] ?? '',
'accountNickname' => $targetAccount['nickname'] ?? '',
'wechatAccountId' => $toWechatAccountId,
'wechatAccountWechatId' => $targetWechatAccount['wechatId'] ?? '',
'updateTime' => time()
]);
return ['success' => true, 'msg' => '转移成功'];
} catch (\Exception $e) {
\think\facade\Log::error("转移群聊异常。群ID: {$groupId}, 目标账号ID: {$toAccountId}, 错误: " . $e->getMessage());
return ['success' => false, 'msg' => $e->getMessage()];
}
}
}