自动建群提交 + 触客宝发布朋友圈优化

This commit is contained in:
wong
2026-01-04 17:03:51 +08:00
parent 08d2a811b7
commit b4c813311b
39 changed files with 16264 additions and 504 deletions

View File

@@ -1020,7 +1020,6 @@ class WebSocketController extends BaseController
// "wechatFriendIds" => $data['wechatFriendIds']
"wechatFriendIds" => [17453051,17453058]
];
$message = $this->sendMessage($params,false);
return json_encode(['code' => 200, 'msg' => '群聊创建成功', 'data' => $message]);
} catch (\Exception $e) {
@@ -1116,6 +1115,7 @@ class WebSocketController extends BaseController
];
}
//chatroomOperateType 4退群 6群公告 5群名称
$params = [
"chatroomOperateType" => !empty($data['chatroomName']) ? 6 : 5,
"cmdType" => "CmdChatroomOperate",
@@ -1125,7 +1125,7 @@ class WebSocketController extends BaseController
"wechatChatroomId" => $data['wechatChatroomId']
];
$message = $this->sendMessage($params,false);
return json_encode(['code' => 200, 'msg' => '群聊创建成功', 'data' => $message]);
return json_encode(['code' => 200, 'msg' => '修改群信息成功', 'data' => $message]);
} catch (\Exception $e) {
Log::error('修改群信息异常: ' . $e->getMessage());
return json_encode(['code' => 500, 'msg' => '修改群信息失败: ' . $e->getMessage()]);

View File

@@ -39,7 +39,7 @@ class WechatChatroomController extends BaseController
'groupId' => $data['groupId'] ?? '',
'wechatChatroomId' => $data['wechatChatroomId'] ?? '',
'memberKeyword' => $data['memberKeyword'] ?? '',
'pageIndex' => $data['pageIndex'] ?? 1,
'pageIndex' => $data['pageIndex'] ?? 0,
'pageSize' => $data['pageSize'] ?? 20
];

View File

@@ -93,27 +93,32 @@ Route::group('v1/', function () {
// 工作台相关
Route::group('workbench', function () {
Route::post('create', 'app\cunkebao\controller\WorkbenchController@create'); // 创建工作台
Route::get('list', 'app\cunkebao\controller\WorkbenchController@getList'); // 获取工作台列表
Route::post('update-status', 'app\cunkebao\controller\WorkbenchController@updateStatus'); // 更新工作台状态
Route::delete('delete', 'app\cunkebao\controller\WorkbenchController@delete'); // 删除工作台
Route::post('copy', 'app\cunkebao\controller\WorkbenchController@copy'); // 拷贝工作台
Route::get('detail', 'app\cunkebao\controller\WorkbenchController@detail'); // 获取工作台详情
Route::post('update', 'app\cunkebao\controller\WorkbenchController@update'); // 更新工作台
Route::get('like-records', 'app\cunkebao\controller\WorkbenchController@getLikeRecords'); // 获取点赞记录列表
Route::get('moments-records', 'app\cunkebao\controller\WorkbenchController@getMomentsRecords'); // 获取朋友圈发布记录列表
Route::get('device-labels', 'app\cunkebao\controller\WorkbenchController@getDeviceLabels'); // 获取设备微信好友标签统计
Route::get('group-list', 'app\cunkebao\controller\WorkbenchController@getGroupList'); // 获取群列表
Route::get('account-list', 'app\cunkebao\controller\WorkbenchController@getAccountList'); // 获取账号列表
Route::get('transfer-friends', 'app\cunkebao\controller\WorkbenchController@getTrafficList'); // 获取账号列表
Route::get('import-contact', 'app\cunkebao\controller\WorkbenchController@getImportContact'); // 获取通讯录导入记录列表
Route::post('create', 'app\cunkebao\controller\workbench\WorkbenchController@create'); // 创建工作台
Route::get('list', 'app\cunkebao\controller\workbench\WorkbenchController@getList'); // 获取工作台列表
Route::post('update-status', 'app\cunkebao\controller\workbench\WorkbenchController@updateStatus'); // 更新工作台状态
Route::delete('delete', 'app\cunkebao\controller\workbench\WorkbenchController@delete'); // 删除工作台
Route::post('copy', 'app\cunkebao\controller\workbench\WorkbenchController@copy'); // 拷贝工作台
Route::get('detail', 'app\cunkebao\controller\workbench\WorkbenchController@detail'); // 获取工作台详情
Route::post('update', 'app\cunkebao\controller\workbench\WorkbenchController@update'); // 更新工作台
Route::get('like-records', 'app\cunkebao\controller\workbench\WorkbenchController@getLikeRecords'); // 获取点赞记录列表
Route::get('moments-records', 'app\cunkebao\controller\workbench\WorkbenchController@getMomentsRecords'); // 获取朋友圈发布记录列表
Route::get('device-labels', 'app\cunkebao\controller\workbench\WorkbenchController@getDeviceLabels'); // 获取设备微信好友标签统计
Route::get('group-list', 'app\cunkebao\controller\workbench\WorkbenchController@getGroupList'); // 获取群列表
Route::get('created-groups-list', 'app\cunkebao\controller\workbench\WorkbenchController@getCreatedGroupsList'); // 获取已创建的群列表(自动建群)
Route::get('created-group-detail', 'app\cunkebao\controller\workbench\WorkbenchController@getCreatedGroupDetail'); // 获取已创建群的详情(自动建群)
Route::post('sync-group-info', 'app\cunkebao\controller\workbench\WorkbenchController@syncGroupInfo'); // 同步群最新信息(包括群成员)
Route::post('modify-group-info', 'app\cunkebao\controller\workbench\WorkbenchController@modifyGroupInfo'); // 修改群名称、群公告
Route::post('quit-group', 'app\cunkebao\controller\workbench\WorkbenchController@quitGroup'); // 退群(自动建群)
Route::get('account-list', 'app\cunkebao\controller\workbench\WorkbenchController@getAccountList'); // 获取账号列表
Route::get('transfer-friends', 'app\cunkebao\controller\workbench\WorkbenchController@getTrafficList'); // 获取账号列表
Route::get('import-contact', 'app\cunkebao\controller\workbench\WorkbenchController@getImportContact'); // 获取通讯录导入记录列表
Route::get('getJdSocialMedia', 'app\cunkebao\controller\WorkbenchController@getJdSocialMedia'); // 获取京东联盟导购媒体
Route::get('getJdPromotionSite', 'app\cunkebao\controller\WorkbenchController@getJdPromotionSite'); // 获取京东联盟广告位
Route::get('changeLink', 'app\cunkebao\controller\WorkbenchController@changeLink'); // 获取京东联盟广告位
Route::get('getJdSocialMedia', 'app\cunkebao\controller\workbench\WorkbenchController@getJdSocialMedia'); // 获取京东联盟导购媒体
Route::get('getJdPromotionSite', 'app\cunkebao\controller\workbench\WorkbenchController@getJdPromotionSite'); // 获取京东联盟广告位
Route::get('changeLink', 'app\cunkebao\controller\workbench\WorkbenchController@changeLink'); // 获取京东联盟广告位
Route::get('group-push-stats', 'app\cunkebao\controller\WorkbenchController@getGroupPushStats'); // 获取群发统计数据
Route::get('group-push-history', 'app\cunkebao\controller\WorkbenchController@getGroupPushHistory'); // 获取推送历史记录列表
Route::get('group-push-stats', 'app\cunkebao\controller\workbench\WorkbenchController@getGroupPushStats'); // 获取群发统计数据
Route::get('group-push-history', 'app\cunkebao\controller\workbench\WorkbenchController@getGroupPushHistory'); // 获取推送历史记录列表
});
// 内容库相关

View File

@@ -643,7 +643,7 @@ class ContentLibraryController extends Controller
// 查询数据
$list = ContentItem::where($where)
->field('id,libraryId,type,title,content,contentAi,contentType,resUrls,urls,friendId,wechatId,wechatChatroomId,createTime,createMomentTime,createMessageTime,coverImage,ossUrls')
->order('createTime DESC,createMomentTime DESC')
->order('createMomentTime DESC,createMessageTime DESC,createTime DESC')
->page($page, $limit)
->select();

View File

@@ -782,8 +782,8 @@ class ChannelController extends BaseController
if (!empty($channelIds)) {
// 构建提现查询条件
$withdrawalWhere = [
['companyId', '=', $companyId],
['channelId', 'in', $channelIds]
['companyId', '=', $companyId],
['channelId', 'in', $channelIds]
];
// 如果不是管理员,只能查看自己创建的提现申请
@@ -817,7 +817,7 @@ class ChannelController extends BaseController
// totalRevenue 不包括驳回rejected状态的金额
if ($status !== DistributionWithdrawal::STATUS_REJECTED) {
$withdrawalStats[$cid]['totalRevenue'] += $amount;
$withdrawalStats[$cid]['totalRevenue'] += $amount;
}
if ($status === DistributionWithdrawal::STATUS_PAID) {

View File

@@ -0,0 +1,108 @@
<?php
namespace app\cunkebao\controller\workbench;
use think\Controller;
use think\Db;
/**
* 工作台 - 自动点赞相关功能
*/
class WorkbenchAutoLikeController extends Controller
{
/**
* 获取点赞记录列表
* @return \think\response\Json
*/
public function getLikeRecords()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$workbenchId = $this->request->param('workbenchId', 0);
$where = [
['wali.workbenchId', '=', $workbenchId]
];
// 查询点赞记录
$list = Db::name('workbench_auto_like_item')->alias('wali')
->join(['s2_wechat_moments' => 'wm'], 'wali.snsId = wm.snsId')
->field([
'wali.id',
'wali.workbenchId',
'wali.momentsId',
'wali.snsId',
'wali.wechatAccountId',
'wali.wechatFriendId',
'wali.createTime as likeTime',
'wm.content',
'wm.resUrls',
'wm.createTime as momentTime',
'wm.userName',
])
->where($where)
->order('wali.createTime', 'desc')
->group('wali.id')
->page($page, $limit)
->select();
// 处理数据
foreach ($list as &$item) {
//处理用户信息
$friend = Db::table('s2_wechat_friend')
->where(['id' => $item['wechatFriendId']])
->field('nickName,avatar')
->find();
if (!empty($friend)) {
$item['friendName'] = $friend['nickName'];
$item['friendAvatar'] = $friend['avatar'];
} else {
$item['friendName'] = '';
$item['friendAvatar'] = '';
}
//处理客服
$friend = Db::table('s2_wechat_account')
->where(['id' => $item['wechatAccountId']])
->field('nickName,avatar')
->find();
if (!empty($friend)) {
$item['operatorName'] = $friend['nickName'];
$item['operatorAvatar'] = $friend['avatar'];
} else {
$item['operatorName'] = '';
$item['operatorAvatar'] = '';
}
// 处理时间格式
$item['likeTime'] = date('Y-m-d H:i:s', $item['likeTime']);
$item['momentTime'] = !empty($item['momentTime']) ? date('Y-m-d H:i:s', $item['momentTime']) : '';
// 处理资源链接
if (!empty($item['resUrls'])) {
$item['resUrls'] = json_decode($item['resUrls'], true);
} else {
$item['resUrls'] = [];
}
}
// 获取总记录数
$total = Db::name('workbench_auto_like_item')->alias('wali')
->where($where)
->count();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $list,
'total' => $total,
'page' => $page,
'limit' => $limit
]
]);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,313 @@
<?php
namespace app\cunkebao\controller\workbench;
use think\Controller;
use think\Db;
use think\facade\Env;
/**
* 工作台 - 辅助功能
*/
class WorkbenchHelperController extends Controller
{
/**
* 获取所有微信好友标签及数量统计
* @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]]);
}
/**
* 获取账号列表
* @return \think\response\Json
*/
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
*/
public function getJdSocialMedia()
{
$data = Db::name('jd_social_media')->order('id DESC')->select();
return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]);
}
/**
* 获取京东联盟广告位
* @return \think\response\Json
*/
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]);
}
/**
* 京东转链-京推推
* @param string $content
* @param string $positionid
* @return string
*/
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']]);
}
}
/**
* 验证内容是否包含链接
* @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;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace app\cunkebao\controller\workbench;
use think\Controller;
use think\Db;
/**
* 工作台 - 联系人导入相关功能
*/
class WorkbenchImportContactController extends Controller
{
/**
* 获取通讯录导入记录列表
* @return \think\response\Json
*/
public function getImportContact()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$workbenchId = $this->request->param('workbenchId', 0);
$where = [
['wici.workbenchId', '=', $workbenchId]
];
// 查询发布记录
$list = Db::name('workbench_import_contact_item')->alias('wici')
->join('traffic_pool tp', 'tp.id = wici.poolId', 'left')
->join('traffic_source tc', 'tc.identifier = tp.identifier', 'left')
->join('wechat_account wa', 'wa.wechatId = tp.wechatId', 'left')
->field([
'wici.id',
'wici.workbenchId',
'wici.createTime',
'tp.identifier',
'tp.mobile',
'tp.wechatId',
'tc.name',
'wa.nickName',
'wa.avatar',
'wa.alias',
])
->where($where)
->order('tc.name DESC,wici.createTime DESC')
->group('tp.identifier')
->page($page, $limit)
->select();
foreach ($list as &$item) {
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
}
// 获取总记录数
$total = Db::name('workbench_import_contact_item')->alias('wici')
->where($where)
->count();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $list,
'total' => $total,
]
]);
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace app\cunkebao\controller\workbench;
use think\Controller;
use think\Db;
/**
* 工作台 - 朋友圈同步相关功能
*/
class WorkbenchMomentsController extends Controller
{
/**
* 获取朋友圈发布记录列表
* @return \think\response\Json
*/
public function getMomentsRecords()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 10);
$workbenchId = $this->request->param('workbenchId', 0);
$where = [
['wmsi.workbenchId', '=', $workbenchId]
];
// 查询发布记录
$list = Db::name('workbench_moments_sync_item')->alias('wmsi')
->join('content_item ci', 'ci.id = wmsi.contentId', 'left')
->join(['s2_wechat_account' => 'wa'], 'wa.id = wmsi.wechatAccountId', 'left')
->field([
'wmsi.id',
'wmsi.workbenchId',
'wmsi.createTime as publishTime',
'ci.contentType',
'ci.content',
'ci.resUrls',
'ci.urls',
'wa.nickName as operatorName',
'wa.avatar as operatorAvatar'
])
->where($where)
->order('wmsi.createTime', 'desc')
->page($page, $limit)
->select();
foreach ($list as &$item) {
$item['resUrls'] = json_decode($item['resUrls'], true);
$item['urls'] = json_decode($item['urls'], true);
}
// 获取总记录数
$total = Db::name('workbench_moments_sync_item')->alias('wmsi')
->where($where)
->count();
return json([
'code' => 200,
'msg' => '获取成功',
'data' => [
'list' => $list,
'total' => $total,
'page' => $page,
'limit' => $limit
]
]);
}
/**
* 获取朋友圈发布统计
* @return \think\response\Json
*/
public function getMomentsStats()
{
$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_moments_sync_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_moments_sync_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'])
]
]
]);
}
}

View File

@@ -0,0 +1,309 @@
<?php
namespace app\cunkebao\controller\workbench;
use think\Controller;
use think\Db;
/**
* 工作台 - 流量分发相关功能
*/
class WorkbenchTrafficController extends Controller
{
/**
* 获取流量分发记录列表
* @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' => 5, // 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 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]);
}
}

View File

@@ -3,12 +3,24 @@ namespace app\job;
use app\chukebao\model\Reply;
use app\chukebao\model\ReplyGroup;
use think\Db;
use think\queue\Job;
class SyncContentJob
{
public function fire(Job $job, $data)
{
$ddd= Db::table('s2_wechat_friend')->where('ownerWechatId','wxid_h7nsh7vxseyn29')->select();
foreach ($ddd as $v) {
$d = Db::table('ck_task_customer')->where('task_id','167')->where('phone',$v['wechatId'])->find();
if (!empty($d) && !in_array($d['status'],[4,5])) {
Db::table('ck_task_customer')->where('id',$d['id'])->update(['status'=>5]);
}
}
exit_data(111);
return true;
/*$ddd= '[{"id":21909,"groupName":"私域运营招聘","sortIndex":240426546,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":8074,"groupName":"存客宝新客户介绍","sortIndex":230,"parentId":0,"replyType":1,"children":[{"id":8081,"groupName":"客户系统上线准备","sortIndex":1,"parentId":8074,"replyType":1,"children":[],"replys":null}],"replys":null},{"id":10441,"groupName":"BOSS直聘","sortIndex":229,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":15111,"groupName":"点了码新客户了解","sortIndex":228,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":12732,"groupName":"测试","sortIndex":219,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":8814,"groupName":"封单话术","sortIndex":176,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":8216,"groupName":"私域合伙人","sortIndex":172,"parentId":0,"replyType":1,"children":[],"replys":null},{"id":6476,"groupName":"新客户了解","sortIndex":119,"parentId":0,"replyType":1,"children":[],"replys":null}]';
$ddd=json_decode($ddd,1);*/

View File

@@ -77,7 +77,8 @@ class WorkbenchGroupCreateJob
{
try {
// 1. 查询启用了建群功能的数据
$workbenches = Workbench::where(['status' => 1, 'type' => 4, 'isDel' => 0,'id' => 315])->order('id desc')->select();
$workbenches = Workbench::where(['status' => 0, 'type' => 4, 'isDel' => 0,'id' => 354])->order('id desc')->select();
foreach ($workbenches as $workbench) {
// 获取工作台配置
$config = WorkbenchGroupCreate::where('workbenchId', $workbench->id)->find();
@@ -86,22 +87,49 @@ class WorkbenchGroupCreateJob
}
// 解析配置
$config['poolGroups'] = json_decode($config['poolGroups'], true);
$config['devices'] = json_decode($config['devices'], true);
$config['poolGroups'] = json_decode($config['poolGroups'] ?? '[]', true) ?: [];
$config['devices'] = json_decode($config['devices'] ?? '[]', true) ?: [];
$config['wechatGroups'] = json_decode($config['wechatGroups'] ?? '[]', true) ?: [];
$config['admins'] = json_decode($config['admins'] ?? '[]', true) ?: [];
if (empty($config['poolGroups']) || empty($config['devices'])) {
// 检查时间限制
if (!$this->isWithinTimeRange($config)) {
continue;
}
// 检查每日建群数量限制
if (!$this->checkDailyLimit($workbench->id, $config)) {
continue;
}
// 检查是否有正在创建中的群,如果有则跳过(避免重复创建)
$creatingCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('status', self::STATUS_CREATING)
->where('groupId', '<>', null) // 有groupId的记录
->group('groupId')
->count();
if ($creatingCount > 0) {
Log::info("工作台ID: {$workbench->id} 有正在创建中的群({$creatingCount}个),跳过本次执行");
continue;
}
if (empty($config['devices'])) {
continue;
}
// 获取群主成员(从设备中获取)
$groupMember = [];
$wechatId = Db::name('device_wechat_login')
->whereIn('deviceId',$config['devices'])
$wechatIds = Db::name('device_wechat_login')
->whereIn('deviceId', $config['devices'])
->where('alive', DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE)
->order('id desc')
->value('wechatId');
if (empty($wechatId)) {
->column('wechatId');
if (empty($wechatIds)) {
continue;
}
$groupMember[] = $wechatId;
$groupMember = array_unique($wechatIds);
// 获取群主好友ID映射所有群主的好友
$groupMemberWechatId = [];
$groupMemberId = [];
@@ -110,6 +138,7 @@ class WorkbenchGroupCreateJob
$friends = Db::table('s2_wechat_friend')
->where('ownerWechatId', $ownerWechatId)
->whereIn('wechatId', $groupMember)
->where('isDeleted', 0)
->field('id,wechatId')
->select();
@@ -120,44 +149,78 @@ class WorkbenchGroupCreateJob
}
}
}
if (empty($groupMemberWechatId)) {
// 如果配置了wechatGroups从指定的群组中获取成员
if (!empty($config['wechatGroups'])) {
$this->addGroupMembersFromWechatGroups($config['wechatGroups'], $groupMember, $groupMemberId, $groupMemberWechatId);
}
if (empty($groupMemberId)) {
continue;
}
// 获取流量池用户
$poolItem = Db::name('traffic_source_package_item')
->whereIn('packageId', $config['poolGroups'])
->group('identifier')
->column('identifier');
if (empty($poolItem)) {
// 获取流量池用户(如果配置了流量池)
$poolItem = [];
if (!empty($config['poolGroups'])) {
$poolItem = Db::name('traffic_source_package_item')
->whereIn('packageId', $config['poolGroups'])
->where('isDel', 0)
->group('identifier')
->column('identifier');
}
// 如果既没有流量池也没有指定群组,跳过
if (empty($poolItem) && empty($config['wechatGroups'])) {
continue;
}
// 获取已入群的用户(排除已成功入群的)111
$groupUser = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('status', 'in', [self::STATUS_SUCCESS, self::STATUS_ADMIN_FRIEND_ADDED, self::STATUS_CREATING])
->whereIn('wechatId', $poolItem)
->group('wechatId')
->column('wechatId');
// 待入群的用户
$joinUser = array_diff($poolItem, $groupUser);
if (empty($joinUser)) {
// 获取已入群的用户(排除已成功入群的)
$groupUser = [];
if (!empty($poolItem)) {
$groupUser = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('status', 'in', [self::STATUS_SUCCESS, self::STATUS_ADMIN_FRIEND_ADDED, self::STATUS_CREATING])
->whereIn('wechatId', $poolItem)
->group('wechatId')
->column('wechatId');
}
// 待入群的用户(从流量池中筛选)
$joinUser = !empty($poolItem) ? array_diff($poolItem, $groupUser) : [];
// 如果流量池用户已用完或没有配置流量池但配置了wechatGroups至少创建一次使用群主成员
if (empty($joinUser) && !empty($config['wechatGroups'])) {
// 如果没有流量池用户创建一个空批次让processBatchUsers处理只有群主成员的情况
$joinUser = []; // 空数组,但会继续执行
}
// 如果既没有流量池用户也没有配置wechatGroups跳过
if (empty($joinUser) && empty($config['wechatGroups'])) {
continue;
}
// 计算随机群人数(不包含管理员,只减去群主成员数)
$groupRandNum = mt_rand($config['groupSizeMin'], $config['groupSizeMax']) - count($groupMember);
// 群主成员数 = 群主好友ID数量
$minGroupSize = max(2, $config['groupSizeMin']); // 至少2人才能建群
$maxGroupSize = max($minGroupSize, $config['groupSizeMax']);
$groupRandNum = mt_rand($minGroupSize, $maxGroupSize) - count($groupMemberId);
if ($groupRandNum <= 0) {
$groupRandNum = 1; // 至少需要1个成员
}
// 分批处理待入群用户
$addGroupUser = [];
$totalRows = count($joinUser);
for ($i = 0; $i < $totalRows; $i += $groupRandNum) {
$batchRows = array_slice($joinUser, $i, $groupRandNum);
if (!empty($batchRows)) {
$addGroupUser[] = $batchRows;
if (!empty($joinUser)) {
$totalRows = count($joinUser);
for ($i = 0; $i < $totalRows; $i += $groupRandNum) {
$batchRows = array_slice($joinUser, $i, $groupRandNum);
if (!empty($batchRows)) {
$addGroupUser[] = $batchRows;
}
}
} else {
// 如果没有流量池用户但配置了wechatGroups创建一个空批次
$addGroupUser[] = [];
}
// 初始化WebSocket
$toAccountId = '';
@@ -229,28 +292,59 @@ class WorkbenchGroupCreateJob
$ownerFriendIdsByAccount = [];
$wechatIds = [];
// 获取群主的好友关系(从流量池中筛选)
$ownerFriends = Db::table('s2_wechat_friend')->alias('f')
->join(['s2_wechat_account' => 'a'], 'f.wechatAccountId=a.id')
->whereIn('f.wechatId', $batchUsers)
->whereIn('a.wechatId', $groupOwnerWechatIds)
->where('f.isDeleted', 0)
->field('f.id,f.wechatId,a.id as wechatAccountId')
->select();
if (empty($ownerFriends)) {
Log::warning("未找到群主的好友跳过。工作台ID: {$workbench->id}");
return;
}
// 按微信账号分组群主好友
foreach ($ownerFriends as $friend) {
$wechatAccountId = $friend['wechatAccountId'];
if (!isset($ownerFriendIdsByAccount[$wechatAccountId])) {
$ownerFriendIdsByAccount[$wechatAccountId] = [];
// 如果batchUsers为空说明没有流量池用户但可能配置了wechatGroups
// 这种情况下,使用群主成员作为基础,按账号分组
if (empty($batchUsers)) {
// 按账号分组群主成员
foreach ($groupMemberId as $memberId) {
$member = Db::table('s2_wechat_friend')->where('id', $memberId)->find();
if ($member) {
$accountWechatId = $member['ownerWechatId'];
$account = Db::table('s2_wechat_account')
->where('wechatId', $accountWechatId)
->field('id')
->find();
if ($account) {
$wechatAccountId = $account['id'];
if (!isset($ownerFriendIdsByAccount[$wechatAccountId])) {
$ownerFriendIdsByAccount[$wechatAccountId] = [];
}
$ownerFriendIdsByAccount[$wechatAccountId][] = $memberId;
$wechatIds[$memberId] = $groupMemberWechatId[$memberId] ?? '';
}
}
}
$ownerFriendIdsByAccount[$wechatAccountId][] = $friend['id'];
$wechatIds[$friend['id']] = $friend['wechatId'];
} else {
// 获取群主的好友关系(从流量池中筛选)
$ownerFriends = Db::table('s2_wechat_friend')->alias('f')
->join(['s2_wechat_account' => 'a'], 'f.wechatAccountId=a.id')
->whereIn('f.wechatId', $batchUsers)
->whereIn('a.wechatId', $groupOwnerWechatIds)
->where('f.isDeleted', 0)
->field('f.id,f.wechatId,a.id as wechatAccountId')
->select();
if (empty($ownerFriends)) {
Log::warning("未找到群主的好友跳过。工作台ID: {$workbench->id}");
return;
}
// 按微信账号分组群主好友
foreach ($ownerFriends as $friend) {
$wechatAccountId = $friend['wechatAccountId'];
if (!isset($ownerFriendIdsByAccount[$wechatAccountId])) {
$ownerFriendIdsByAccount[$wechatAccountId] = [];
}
$ownerFriendIdsByAccount[$wechatAccountId][] = $friend['id'];
$wechatIds[$friend['id']] = $friend['wechatId'];
}
}
// 如果没有找到任何好友,跳过
if (empty($ownerFriendIdsByAccount)) {
Log::warning("未找到任何群主好友或成员跳过。工作台ID: {$workbench->id}");
return;
}
// 4. 遍历每个微信账号,创建群
@@ -279,21 +373,47 @@ class WorkbenchGroupCreateJob
}
// 4.3 限制群主好友数量(按随机群人数)
$limitedOwnerFriendIds = array_slice($ownerFriendIds, 0, $groupRandNum);
// 如果ownerFriendIds只包含群主成员没有流量池用户则不需要限制
$limitedOwnerFriendIds = $ownerFriendIds;
if (count($ownerFriendIds) > $groupRandNum) {
$limitedOwnerFriendIds = array_slice($ownerFriendIds, 0, $groupRandNum);
}
// 4.4 创建群:管理员 + 群主成员 + 群主好友(从流量池筛选)
$createFriendIds = array_merge($currentAdminFriendIds, $currentGroupMemberIds, $limitedOwnerFriendIds);
// 合并时去重,避免重复添加群主成员
$createFriendIds = array_merge($currentAdminFriendIds, $currentGroupMemberIds);
foreach ($limitedOwnerFriendIds as $friendId) {
if (!in_array($friendId, $createFriendIds)) {
$createFriendIds[] = $friendId;
}
}
// 微信建群至少需要2个人
if (count($createFriendIds) < 2) {
Log::warning("建群好友数量不足跳过。工作台ID: {$workbench->id}, 微信账号ID: {$wechatAccountId}");
Log::warning("建群好友数量不足至少需要2人跳过。工作台ID: {$workbench->id}, 微信账号ID: {$wechatAccountId}, 当前人数: " . count($createFriendIds));
continue;
}
// 4.5 生成群名称
// 4.5 检查当前账号是否有正在创建中的群,如果有则跳过
$creatingGroupCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('wechatAccountId', $wechatAccountId)
->where('status', self::STATUS_CREATING)
->where('groupId', '<>', null)
->group('groupId')
->count();
if ($creatingGroupCount > 0) {
Log::info("工作台ID: {$workbench->id}, 微信账号ID: {$wechatAccountId} 有正在创建中的群({$creatingGroupCount}个),跳过本次创建");
continue;
}
// 4.6 生成群名称
$existingGroupCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbench->id)
->where('wechatAccountId', $wechatAccountId)
->where('status', self::STATUS_SUCCESS)
->where('groupId', '<>', null) // 排除groupId为NULL的记录
->group('groupId')
->count();
@@ -301,7 +421,7 @@ class WorkbenchGroupCreateJob
? $config['groupNameTemplate'] . ($existingGroupCount + 1) . '群'
: $config['groupNameTemplate'];
// 4.6 调用建群接口
// 4.7 调用建群接口
$createTime = time();
$createResult = $webSocket->CmdChatroomCreate([
'chatroomName' => $chatroomName,
@@ -311,18 +431,65 @@ class WorkbenchGroupCreateJob
$createResultData = json_decode($createResult, true);
// 4.7 解析建群结果获取群ID
$chatroomId = 0;
// 4.8 解析建群结果获取群ID
// chatroomId: varchar(64) - 微信的群聊ID字符串
// groupId: int(10) - 数据库中的群组ID整数
$chatroomId = null; // 微信群聊ID字符串
$groupId = 0; // 数据库群组ID整数
$tempGroupId = null; // 临时群标识,用于轮询查询
if (!empty($createResultData) && isset($createResultData['code']) && $createResultData['code'] == 200) {
// 尝试从返回数据中获取群ID根据实际API返回格式调整
if (isset($createResultData['data']['chatroomId'])) {
$chatroomId = $createResultData['data']['chatroomId'];
// API返回的是chatroomId(字符串)
$chatroomId = (string)$createResultData['data']['chatroomId'];
// 通过chatroomId查询数据库获取groupId
$group = Db::name('wechat_group')
->where('chatroomId', $chatroomId)
->where('deleteTime', 0)
->find();
if ($group) {
$groupId = intval($group['id']);
}
} elseif (isset($createResultData['data']['id'])) {
$chatroomId = $createResultData['data']['id'];
// API返回的是数据库ID整数
$groupId = intval($createResultData['data']['id']);
// 通过groupId查询chatroomId
$group = Db::name('wechat_group')
->where('id', $groupId)
->where('deleteTime', 0)
->find();
if ($group && !empty($group['chatroomId'])) {
$chatroomId = (string)$group['chatroomId'];
}
}
// 如果有临时标识,保存用于轮询
if (isset($createResultData['data']['tempId'])) {
$tempGroupId = $createResultData['data']['tempId'];
}
}
// 4.8 记录创建请求
// 4.9 如果建群接口没有立即返回群ID进行同步轮询检查
if ($groupId == 0) {
// 获取账号的微信ID群主微信ID
$accountWechatId = Db::table('s2_wechat_account')
->where('id', $wechatAccountId)
->value('wechatId');
if (!empty($accountWechatId)) {
$pollResult = $this->pollGroupCreation($chatroomName, $accountWechatId, $wechatAccountId, $tempGroupId);
if ($pollResult && is_array($pollResult)) {
$groupId = intval($pollResult['groupId'] ?? 0);
$chatroomId = !empty($pollResult['chatroomId']) ? (string)$pollResult['chatroomId'] : null;
} elseif ($pollResult > 0) {
// 兼容旧返回值只返回groupId
$groupId =0;
$chatroomId = null;
}
}
}
// 4.10 记录创建请求
$installData = [];
foreach ($createFriendIds as $friendId) {
$memberType = in_array($friendId, $currentAdminFriendIds)
@@ -333,20 +500,21 @@ class WorkbenchGroupCreateJob
'workbenchId' => $workbench->id,
'friendId' => $friendId,
'wechatId' => $wechatIds[$friendId] ?? ($groupMemberWechatId[$friendId] ?? ''),
'groupId' => $chatroomId,
'groupId' => $groupId > 0 ? $groupId : null, // int类型
'wechatAccountId' => $wechatAccountId,
'status' => $chatroomId > 0 ? self::STATUS_SUCCESS : self::STATUS_CREATING,
'status' => $groupId > 0 ? self::STATUS_SUCCESS : self::STATUS_FAILED,
'memberType' => $memberType,
'retryCount' => 0,
'chatroomId' => $chatroomId > 0 ? $chatroomId : null,
'chatroomId' => $chatroomId, // varchar类型
'createTime' => $createTime,
];
}
Db::name('workbench_group_create_item')->insertAll($installData);
// 5. 如果群创建成功,拉管理员的好友进群
if ($chatroomId > 0 && !empty($currentAdminFriendIds)) {
$this->inviteAdminFriends($workbench, $config, $batchUsers, $currentAdminFriendIds, $chatroomId, $wechatAccountId, $wechatIds, $createTime, $webSocket);
// 注意拉人接口需要chatroomId字符串而不是groupId整数
if (!empty($chatroomId) && !empty($currentAdminFriendIds)) {
$this->inviteAdminFriends($workbench, $config, $batchUsers, $currentAdminFriendIds, $chatroomId, $groupId, $wechatAccountId, $wechatIds, $createTime, $webSocket);
}
}
}
@@ -357,13 +525,14 @@ class WorkbenchGroupCreateJob
* @param array $config 配置
* @param array $batchUsers 批次用户流量池微信ID数组
* @param array $adminFriendIds 管理员好友ID数组
* @param int $chatroomId 群ID
* @param string $chatroomId 群ID字符串用于API调用
* @param int $groupId 数据库群组ID整数
* @param int $wechatAccountId 微信账号ID
* @param array $wechatIds 好友ID到微信ID的映射
* @param int $createTime 创建时间
* @param WebSocketController $webSocket WebSocket实例
*/
protected function inviteAdminFriends($workbench, $config, $batchUsers, $adminFriendIds, $chatroomId, $wechatAccountId, $wechatIds, $createTime, $webSocket)
protected function inviteAdminFriends($workbench, $config, $batchUsers, $adminFriendIds, $chatroomId, $groupId, $wechatAccountId, $wechatIds, $createTime, $webSocket)
{
// 获取管理员的微信ID列表
$adminWechatIds = [];
@@ -399,7 +568,7 @@ class WorkbenchGroupCreateJob
$wechatIds[$friend['id']] = $friend['wechatId'];
}
// 调用拉人接口
// 调用拉人接口使用chatroomId字符串
$inviteResult = $webSocket->CmdChatroomInvite([
'wechatChatroomId' => $chatroomId,
'wechatFriendIds' => $adminFriendIdsToInvite
@@ -415,12 +584,12 @@ class WorkbenchGroupCreateJob
'workbenchId' => $workbench->id,
'friendId' => $friendId,
'wechatId' => $wechatIds[$friendId] ?? '',
'groupId' => $chatroomId,
'groupId' => $groupId > 0 ? $groupId : null, // int类型
'wechatAccountId' => $wechatAccountId,
'status' => $inviteSuccess ? self::STATUS_ADMIN_FRIEND_ADDED : self::STATUS_FAILED,
'memberType' => self::MEMBER_TYPE_ADMIN_FRIEND,
'retryCount' => 0,
'chatroomId' => $chatroomId,
'chatroomId' => $chatroomId, // varchar类型
'createTime' => $createTime,
];
}
@@ -429,40 +598,209 @@ class WorkbenchGroupCreateJob
if ($inviteSuccess) {
// 去除成功日志,减少日志空间消耗
} else {
Log::warning("管理员好友拉入失败。工作台ID: {$workbench->id}, 群ID: {$chatroomId}");
Log::warning("管理员好友拉入失败。工作台ID: {$workbench->id}, 群组ID: {$groupId}, 群聊ID: {$chatroomId}");
}
}
/**
* 获取设备列表
* @param Workbench $workbench 工作台
* @param WorkbenchGroupPush $config 配置
* @return array|bool
* 轮询检查群是否创建成功
* @param string $chatroomName 群名称
* @param string $ownerWechatId 群主微信ID
* @param int $wechatAccountId 微信账号ID
* @param string|null $tempGroupId 临时群标识(如果有)
* @return array|int 返回数组包含groupId和chatroomId或只返回groupId兼容旧代码如果未找到返回0
*/
protected function isCreate($workbench, $config, $groupIds = [])
protected function pollGroupCreation($chatroomName, $ownerWechatId, $wechatAccountId, $tempGroupId = null)
{
// 检查发送间隔新逻辑根据startTime、endTime、maxPerDay动态计算
$maxAttempts = 10; // 最多查询10次
$interval = 5; // 每次间隔5秒
// 获取账号IDaccountId和微信账号的微信IDwechatAccountWechatId用于查询s2_wechat_chatroom表
$accountInfo = Db::table('s2_wechat_account')
->where('id', $wechatAccountId)
->field('id,wechatId')
->find();
$accountId = $accountInfo['id'] ?? null;
$wechatAccountWechatId = $accountInfo['wechatId'] ?? null;
if (empty($accountId) && empty($wechatAccountWechatId)) {
Log::warning("无法获取账号ID和微信账号ID跳过轮询。微信账号ID: {$wechatAccountId}");
return 0;
}
// 获取授权信息(用于调用同步接口)
$username = Env::get('api.username2', '');
$password = Env::get('api.password2', '');
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
// 等待5秒第一次立即查询后续等待
if ($attempt > 1) {
sleep($interval);
}
// 1. 先调用接口同步最新的群组信息
try {
$chatroomController = new \app\api\controller\WechatChatroomController();
// 构建同步参数
$syncData = [
'wechatAccountKeyword' => $ownerWechatId, // 通过群主微信ID筛选
'isDeleted' => false,
'pageIndex' => 0,
'pageSize' => 5 // 获取足够多的数据
];
// 调用getlist方法同步数据内部调用isInner=true
$chatroomController->getlist($syncData, true, 0);
} catch (\Exception $e) {
Log::warning("同步群组信息失败: " . $e->getMessage());
// 即使同步失败,也继续查询本地数据
}
// 2. 查询本地表 s2_wechat_chatroom
// 计算5分钟前的时间戳
$fiveMinutesAgo = time() - 300; // 5分钟 = 300秒
$now = time();
// 查询群聊通过群名称、账号ID或微信账号ID和创建时间查询
// 如果accountId不为空优先使用accountId查询如果accountId为空则使用wechatAccountWechatId查询
$chatroom = Db::table('s2_wechat_chatroom')
->where('nickname', $chatroomName)
->where('isDeleted', 0)
->where('createTime', '>=', $fiveMinutesAgo) // 创建时间在5分钟内
->where('createTime', '<=', $now)
->where('wechatAccountWechatId', $wechatAccountWechatId)
->order('createTime', 'desc')
->find();
// 如果找到了群聊返回群ID和chatroomId
if ($chatroom && !empty($chatroom['id'])) {
$chatroomId = !empty($chatroom['chatroomId']) ? (string)$chatroom['chatroomId'] : null;
// 如果有chatroomId尝试查询wechat_group表获取groupId
$groupId = $chatroom['id'];
Log::info("轮询检查群创建成功。群名称: {$chatroomName}, 群聊ID: {$chatroom['id']}, chatroomId: {$chatroomId}, 群组ID: {$groupId}, 尝试次数: {$attempt}");
return [
'groupId' => $groupId > 0 ? $groupId : intval($chatroom['id']), // 如果没有groupId使用chatroom的id
'chatroomId' => $chatroomId ?: (string)$chatroom['id']
];
}
Log::debug("轮询检查群创建中。群名称: {$chatroomName}, 尝试次数: {$attempt}/{$maxAttempts}");
}
// 10次查询后仍未找到返回0表示失败
Log::warning("轮询检查群创建失败,已查询{$maxAttempts}次仍未找到群组。群名称: {$chatroomName}, 群主微信ID: {$ownerWechatId}, 账号ID: {$accountId}");
return 0;
}
/**
* 检查是否在时间范围内
* @param array $config 配置
* @return bool
*/
protected function isWithinTimeRange($config)
{
if (empty($config['startTime']) || empty($config['endTime'])) {
return true; // 如果没有配置时间,则允许执行
}
$today = date('Y-m-d');
$startTimestamp = strtotime($today . ' ' . $config['startTime'] . ':00');
$endTimestamp = strtotime($today . ' ' . $config['endTime'] . ':00');
// 如果时间不符,则跳过
if ($startTimestamp > time() || $endTimestamp < time() || empty($groupIds)) {
$currentTime = time();
// 如果开始时间大于当前时间,还未到执行时间
if ($startTimestamp > $currentTime) {
return false;
}
// 查询今日建群数量
$count = Db::name('wechat_group')
->whereIn('id', $groupIds)
->whereTime('createTime', 'between', [$startTimestamp, $endTimestamp])
->count();
if ($count >= $config['maxGroupsPerDay']) {
// 如果结束时间小于当前时间,已过执行时间
if ($endTimestamp < $currentTime) {
return false;
}
return true;
}
/**
* 检查每日建群数量限制
* @param int $workbenchId 工作台ID
* @param array $config 配置
* @return bool
*/
protected function checkDailyLimit($workbenchId, $config)
{
if (empty($config['maxGroupsPerDay']) || $config['maxGroupsPerDay'] <= 0) {
return true; // 如果没有配置限制,则允许执行
}
$today = date('Y-m-d');
$startTimestamp = strtotime($today . ' 00:00:00');
$endTimestamp = strtotime($today . ' 23:59:59');
// 查询今日已创建的群数量(状态为成功)
$todayCount = Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('status', self::STATUS_SUCCESS)
->where('groupId', '<>', null) // 排除groupId为NULL的记录
->where('createTime', 'between', [$startTimestamp, $endTimestamp])
->group('groupId')
->count();
return $todayCount < $config['maxGroupsPerDay'];
}
/**
* 从指定的微信群组中获取成员
* @param array $wechatGroups 群组ID数组可能是好友ID或群组ID
* @param array $groupMember 群主成员微信ID数组引用传递
* @param array $groupMemberId 群主成员好友ID数组引用传递
* @param array $groupMemberWechatId 群主成员微信ID映射引用传递
*/
protected function addGroupMembersFromWechatGroups($wechatGroups, &$groupMember, &$groupMemberId, &$groupMemberWechatId)
{
foreach ($wechatGroups as $groupId) {
if (is_numeric($groupId)) {
// 数字ID可能是好友ID查询好友信息
$friend = Db::table('s2_wechat_friend')
->where('id', $groupId)
->where('isDeleted', 0)
->field('id,wechatId,ownerWechatId')
->find();
if ($friend) {
// 添加到群主成员
if (!in_array($friend['ownerWechatId'], $groupMember)) {
$groupMember[] = $friend['ownerWechatId'];
}
if (!isset($groupMemberWechatId[$friend['id']])) {
$groupMemberWechatId[$friend['id']] = $friend['wechatId'];
$groupMemberId[] = $friend['id'];
}
} else {
// 如果不是好友ID可能是群组ID查询群组信息
$group = Db::name('wechat_group')
->where('id', $groupId)
->where('deleteTime', 0)
->field('ownerWechatId')
->find();
if ($group && !in_array($group['ownerWechatId'], $groupMember)) {
$groupMember[] = $group['ownerWechatId'];
}
}
} else {
// 字符串ID手动创建的群组可能是wechatId
if (!in_array($groupId, $groupMember)) {
$groupMember[] = $groupId;
}
}
}
}
/**
* 记录任务开始
@@ -510,4 +848,5 @@ class WorkbenchGroupCreateJob
return false;
}
}