场景获客支持拉群

This commit is contained in:
wong
2026-01-08 10:45:41 +08:00
parent b2e84a2259
commit e469537ac5
13 changed files with 1089 additions and 10564 deletions

View File

@@ -1,11 +1,12 @@
// 步骤定义 - 个步骤
// 步骤定义 - 个步骤
import { DeviceSelectionItem } from "@/components/DeviceSelection/data";
import { GroupSelectionItem } from "@/components/GroupSelection/data";
export const steps = [
{ id: 1, title: "步骤一", subtitle: "基础设置" },
{ id: 2, title: "步骤二", subtitle: "好友申请设置" },
{ id: 3, title: "步骤三", subtitle: "渠道设置" },
{ id: 4, title: "步骤四", subtitle: "消息设置" },
{ id: 4, title: "步骤四", subtitle: "拉群设置" },
{ id: 5, title: "步骤五", subtitle: "消息设置" },
];
// 类型定义
@@ -31,6 +32,10 @@ export interface FormData {
wechatGroups: string[];
wechatGroupsOptions: GroupSelectionItem[];
messagePlans: any[];
// 拉群设置
groupInviteEnabled?: boolean;
groupName?: string;
fixedGroupMembers?: any[]; // 固定群成员,使用好友选择组件结构
// 分销相关
distributionEnabled?: boolean;
// 选中的分销渠道ID列表前端使用提交时转为 distributionChannels
@@ -72,6 +77,9 @@ export const defFormData: FormData = {
wechatGroupsOptions: [],
contentGroups: [],
contentGroupsOptions: [],
groupInviteEnabled: false,
groupName: "",
fixedGroupMembers: [],
distributionEnabled: false,
distributionChannelIds: [],
distributionChannelsOptions: [],

View File

@@ -5,6 +5,7 @@ import NavCommon from "@/components/NavCommon";
import BasicSettings from "./steps/BasicSettings";
import FriendRequestSettings from "./steps/FriendRequestSettings";
import DistributionSettings from "./steps/DistributionSettings";
import GroupSettings from "./steps/GroupSettings";
import MessageSettings from "./steps/MessageSettings";
import Layout from "@/components/Layout/Layout";
import StepIndicator from "@/components/StepIndicator";
@@ -112,6 +113,15 @@ export default function NewPlan() {
wechatGroupsOptions: detail.wechatGroupsOptions ?? [],
contentGroups: detail.contentGroups ?? [],
contentGroupsOptions: detail.contentGroupsOptions ?? [],
// 拉群设置
groupInviteEnabled: detail.groupInviteEnabled ?? false,
groupName: detail.groupName ?? "",
// 优先使用后端返回的 options完整好友信息否则退回到 ID 数组或旧字段
fixedGroupMembers:
detail.groupFixedMembersOptions ??
detail.fixedGroupMembers ??
detail.groupFixedMembers ??
[],
status: detail.status ?? 0,
messagePlans: detail.messagePlans ?? [],
// 分销相关数据回填
@@ -196,11 +206,26 @@ export default function NewPlan() {
submitData.distributionEnabled = false;
}
// 拉群设置字段转换
if (formData.groupInviteEnabled) {
submitData.groupInviteEnabled = true;
submitData.groupName = formData.groupName || "";
// 后端期望的字段groupFixedMembers使用好友ID数组
submitData.groupFixedMembers = (formData.fixedGroupMembers || []).map(
(f: any) => f.id,
);
} else {
submitData.groupInviteEnabled = false;
submitData.groupName = "";
submitData.groupFixedMembers = [];
}
// 移除前端使用的字段,避免提交到后端
delete submitData.distributionChannelIds;
delete submitData.distributionChannelsOptions;
delete submitData.distributionCustomerReward;
delete submitData.distributionAddReward;
delete submitData.fixedGroupMembers;
if (isEdit && planId) {
// 编辑:拼接后端需要的完整参数
@@ -272,6 +297,8 @@ export default function NewPlan() {
/>
);
case 4:
return <GroupSettings formData={formData} onChange={onChange} />;
case 5:
return <MessageSettings formData={formData} onChange={onChange} />;
default:
return null;

View File

@@ -0,0 +1,110 @@
import React from "react";
import { Input, Switch } from "antd";
import styles from "./base.module.scss";
import FriendSelection from "@/components/FriendSelection";
import type { FriendSelectionItem } from "@/components/FriendSelection/data";
interface GroupSettingsProps {
formData: any;
onChange: (data: any) => void;
}
const GroupSettings: React.FC<GroupSettingsProps> = ({ formData, onChange }) => {
const handleToggleGroupInvite = (value: boolean) => {
onChange({
...formData,
groupInviteEnabled: value,
});
};
const handleGroupNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
onChange({
...formData,
groupName: e.target.value,
});
};
const handleFixedMembersSelect = (friends: FriendSelectionItem[]) => {
onChange({
...formData,
fixedGroupMembers: friends,
});
};
return (
<div className={styles["basic-container"]}>
<div
style={{
marginBottom: 16,
padding: 16,
borderRadius: 8,
border: "1px solid #f0f0f0",
background: "#fff",
}}
>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
marginBottom: 12,
}}
>
<div>
<div
style={{
fontSize: 16,
fontWeight: 500,
marginBottom: 4,
}}
>
</div>
<div style={{ fontSize: 12, color: "#8c8c8c" }}>
</div>
</div>
<Switch
checked={!!formData.groupInviteEnabled}
onChange={handleToggleGroupInvite}
/>
</div>
{formData.groupInviteEnabled && (
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<Input
placeholder="请输入群名称"
value={formData.groupName || ""}
onChange={handleGroupNameChange}
/>
{/* 固定成员选择,复用好友选择组件 */}
<div>
<div
style={{
fontSize: 13,
marginBottom: 4,
color: "#595959",
}}
>
</div>
<FriendSelection
selectedOptions={
(formData.fixedGroupMembers || []) as FriendSelectionItem[]
}
onSelect={handleFixedMembersSelect}
placeholder="选择固定群成员"
showSelectedList={true}
// 根据已选择设备过滤好友列表
deviceIds={formData.deviceGroups || []}
enableDeviceFilter={true}
/>
</div>
</div>
)}
</div>
</div>
);
};
export default GroupSettings;

1
Server/.gitignore vendored
View File

@@ -16,3 +16,4 @@ nginx.htaccess
.cursor/
thinkphp/
public/static/
*.log

View File

@@ -1017,8 +1017,8 @@ class WebSocketController extends BaseController
"seq" => time(),
"wechatAccountId" => $data['wechatAccountId'],
"chatroomName" => $data['chatroomName'],
// "wechatFriendIds" => $data['wechatFriendIds']
"wechatFriendIds" => [17453051,17453058]
"wechatFriendIds" => $data['wechatFriendIds']
//"wechatFriendIds" => [17453051,17453058]
];
$message = $this->sendMessage($params,false);
return json_encode(['code' => 200, 'msg' => '群聊创建成功', 'data' => $message]);

View File

@@ -124,6 +124,86 @@ class GetAddFriendPlanDetailV1Controller extends Controller
$msgConf = json_decode($plan['msgConf'], true) ?: [];
$tagConf = json_decode($plan['tagConf'], true) ?: [];
// 处理拉群固定成员为数组,并构造下拉 options
if (!empty($plan['groupFixedMembers'])) {
$fixedMembers = json_decode($plan['groupFixedMembers'], true);
$plan['groupFixedMembers'] = is_array($fixedMembers) ? $fixedMembers : [];
} else {
$plan['groupFixedMembers'] = [];
}
// groupFixedMembersOptions参考 workbench 中好友 options 的结构,返回完整好友信息
$groupFixedMembersOptions = [];
if (!empty($plan['groupFixedMembers'])) {
$friendIds = [];
$manualIds = [];
foreach ($plan['groupFixedMembers'] as $member) {
if (is_numeric($member)) {
$friendIds[] = intval($member);
} else {
$manualIds[] = $member;
}
}
// 数字 ID从 s2_wechat_friend 中查询好友信息
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);
$groupFixedMembersOptions = array_merge($groupFixedMembersOptions, $friendList);
}
// 手动 ID仅返回基础结构标记 isManual=1
if (!empty($manualIds)) {
foreach ($manualIds as $mid) {
$groupFixedMembersOptions[] = [
'id' => $mid,
'wechatId' => $mid,
'nickname' => $mid,
'avatar' => '',
'alias' => '',
'gender' => 0,
'phone' => '',
'accountNickname' => '',
'account' => '',
'username' => '',
'ownerNickname' => '',
'ownerAlias' => '',
'ownerWechatId' => '',
'createTime' => '',
'updateTime' => '',
'deleteTime' => '',
'isManual' => 1,
];
}
}
}
$sceneConf['groupFixedMembersOptions'] = $groupFixedMembersOptions;
// 处理分销配置
$distributionConfig = $sceneConf['distribution'] ?? [
'enabled' => false,

View File

@@ -69,6 +69,21 @@ class PostCreateAddFriendPlanV1Controller extends BaseController
return ResponseHelper::error('请选择设备', 400);
}
// 拉群配置校验
// groupInviteEnabled拉群开关0/1
// groupName群名称
// groupFixedMembers固定成员数组
$groupInviteEnabled = !empty($params['groupInviteEnabled']) ? 1 : 0;
if ($groupInviteEnabled) {
if (empty($params['groupName'])) {
return ResponseHelper::error('拉群群名不能为空', 400);
}
if (empty($params['groupFixedMembers']) || !is_array($params['groupFixedMembers'])) {
return ResponseHelper::error('固定成员不能为空', 400);
}
}
$companyId = $this->getUserInfo('companyId');
// 处理分销配置
@@ -114,7 +129,11 @@ class PostCreateAddFriendPlanV1Controller extends BaseController
$sceneConf['distributionEnabled'],
$sceneConf['distributionChannels'],
$sceneConf['customerRewardAmount'],
$sceneConf['addFriendRewardAmount']
$sceneConf['addFriendRewardAmount'],
// 拉群相关字段单独存表,不放到 sceneConf
$sceneConf['groupInviteEnabled'],
$sceneConf['groupName'],
$sceneConf['groupFixedMembers']
);
// 将分销配置添加到sceneConf中
@@ -131,6 +150,12 @@ class PostCreateAddFriendPlanV1Controller extends BaseController
'userId' => $this->getUserInfo('id'),
'companyId' => $this->getUserInfo('companyId'),
'status' => !empty($params['status']) ? 1 : 0,
// 拉群配置
'groupInviteEnabled' => $groupInviteEnabled,
'groupName' => $params['groupName'] ?? '',
'groupFixedMembers' => !empty($params['groupFixedMembers'])
? json_encode($params['groupFixedMembers'], JSON_UNESCAPED_UNICODE)
: json_encode([], JSON_UNESCAPED_UNICODE),
'apiKey' => $this->generateApiKey(), // 生成API密钥
'createTime' => time(),
'updateTime' => time(),

View File

@@ -39,6 +39,18 @@ class PostUpdateAddFriendPlanV1Controller extends BaseController
return ResponseHelper::error('请选择设备', 400);
}
// 拉群配置校验
$groupInviteEnabled = !empty($params['groupInviteEnabled']) ? 1 : 0;
if ($groupInviteEnabled) {
if (empty($params['groupName'])) {
return ResponseHelper::error('拉群群名不能为空', 400);
}
if (empty($params['groupFixedMembers']) || !is_array($params['groupFixedMembers'])) {
return ResponseHelper::error('固定成员不能为空', 400);
}
}
// 检查计划是否存在
$plan = Db::name('customer_acquisition_task')
->where('id', $params['planId'])
@@ -94,7 +106,11 @@ class PostUpdateAddFriendPlanV1Controller extends BaseController
$sceneConf['distributionEnabled'],
$sceneConf['distributionChannels'],
$sceneConf['customerRewardAmount'],
$sceneConf['addFriendRewardAmount']
$sceneConf['addFriendRewardAmount'],
// 拉群相关字段单独存表,不放到 sceneConf
$sceneConf['groupInviteEnabled'],
$sceneConf['groupName'],
$sceneConf['groupFixedMembers']
);
// 将分销配置添加到sceneConf中
@@ -109,6 +125,12 @@ class PostUpdateAddFriendPlanV1Controller extends BaseController
'msgConf' => json_encode($msgConf, JSON_UNESCAPED_UNICODE),
'tagConf' => json_encode($tagConf, JSON_UNESCAPED_UNICODE),
'status' => !empty($params['status']) ? 1 : 0,
// 拉群配置
'groupInviteEnabled' => $groupInviteEnabled,
'groupName' => $params['groupName'] ?? '',
'groupFixedMembers' => !empty($params['groupFixedMembers'])
? json_encode($params['groupFixedMembers'], JSON_UNESCAPED_UNICODE)
: json_encode([], JSON_UNESCAPED_UNICODE),
'updateTime' => time(),
];

View File

@@ -505,7 +505,7 @@ class WorkbenchController extends Controller
$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');
$query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins,executorId');
},
'importContact' => function ($query) {
$query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime');
@@ -642,6 +642,70 @@ class WorkbenchController extends Controller
'totalMembersCount' => $totalMembersCount
];
// 如果 executorId 有值,查询设备详情(格式和 deviceGroupsOptions 一样,但返回一维数组)
$executorId = !empty($workbench->config->executorId) ? intval($workbench->config->executorId) : 0;
$executor = null;
if (!empty($executorId)) {
// 查询设备基本信息
$device = Db::table('s2_device')
->where('id', $executorId)
->where('isDeleted', 0)
->field('id,imei,memo,alive,wechatAccounts')
->find();
if (!empty($device)) {
// 查询关联的微信账号(通过 currentDeviceId
$wechatAccount = Db::table('s2_wechat_account')
->where('currentDeviceId', $executorId)
->field('wechatId,nickname,alias,avatar,totalFriend')
->find();
// 解析 wechatAccounts JSON 字段
$wechatAccountsJson = [];
if (!empty($device['wechatAccounts'])) {
$wechatAccountsJson = json_decode($device['wechatAccounts'], true);
if (!is_array($wechatAccountsJson)) {
$wechatAccountsJson = [];
}
}
// 优先使用 s2_wechat_account 表的数据,如果没有则使用 wechatAccounts JSON 中的第一个
$wechatId = '';
$nickname = '';
$alias = '';
$avatar = '';
$totalFriend = 0;
if (!empty($wechatAccount)) {
$wechatId = $wechatAccount['wechatId'] ?? '';
$nickname = $wechatAccount['nickname'] ?? '';
$alias = $wechatAccount['alias'] ?? '';
$avatar = $wechatAccount['avatar'] ?? '';
$totalFriend = intval($wechatAccount['totalFriend'] ?? 0);
} elseif (!empty($wechatAccountsJson) && is_array($wechatAccountsJson) && count($wechatAccountsJson) > 0) {
$firstWechat = $wechatAccountsJson[0];
$wechatId = $firstWechat['wechatId'] ?? '';
$nickname = $firstWechat['wechatNickname'] ?? '';
$alias = $firstWechat['alias'] ?? '';
$avatar = $firstWechat['wechatAvatar'] ?? '';
$totalFriend = 0; // JSON 中没有 totalFriend 字段
}
$executor = [
'id' => $device['id'],
'imei' => $device['imei'] ?? '',
'memo' => $device['memo'] ?? '',
'alive' => $device['alive'] ?? 0,
'wechatId' => $wechatId,
'nickname' => $nickname,
'alias' => $alias,
'avatar' => $avatar,
'totalFriend' => $totalFriend
];
}
}
$workbench->config->executor = $executor;
unset($workbench->groupCreate, $workbench->group_create);
}
break;
@@ -725,23 +789,83 @@ class WorkbenchController extends Controller
//获取设备信息
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')
// 查询设备基本信息(包含 wechatAccounts JSON 字段)
$devices = Db::table('s2_device')
->whereIn('id', $workbench->config->deviceGroups)
->where('isDeleted', 0)
->field('id,imei,memo,alive,wechatAccounts')
->order('id desc')
->select();
foreach ($deviceList as &$device) {
$curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find();
$device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0;
$deviceList = [];
if (!empty($devices)) {
// 批量查询关联的微信账号(通过 currentDeviceId
$deviceIds = array_column($devices, 'id');
$wechatAccounts = Db::table('s2_wechat_account')
->whereIn('currentDeviceId', $deviceIds)
->field('currentDeviceId,wechatId,nickname,alias,avatar,totalFriend')
->select();
// 将微信账号按设备ID分组
$wechatAccountsMap = [];
foreach ($wechatAccounts as $wa) {
$deviceId = $wa['currentDeviceId'];
if (!isset($wechatAccountsMap[$deviceId])) {
$wechatAccountsMap[$deviceId] = $wa;
}
}
// 处理每个设备
foreach ($devices as $device) {
$deviceId = $device['id'];
// 查询关联的微信账号(通过 currentDeviceId
$wechatAccount = $wechatAccountsMap[$deviceId] ?? null;
// 解析 wechatAccounts JSON 字段
$wechatAccountsJson = [];
if (!empty($device['wechatAccounts'])) {
$wechatAccountsJson = json_decode($device['wechatAccounts'], true);
if (!is_array($wechatAccountsJson)) {
$wechatAccountsJson = [];
}
}
// 优先使用 s2_wechat_account 表的数据,如果没有则使用 wechatAccounts JSON 中的第一个
$wechatId = '';
$nickname = '';
$alias = '';
$avatar = '';
$totalFriend = 0;
if (!empty($wechatAccount)) {
$wechatId = $wechatAccount['wechatId'] ?? '';
$nickname = $wechatAccount['nickname'] ?? '';
$alias = $wechatAccount['alias'] ?? '';
$avatar = $wechatAccount['avatar'] ?? '';
$totalFriend = intval($wechatAccount['totalFriend'] ?? 0);
} elseif (!empty($wechatAccountsJson) && is_array($wechatAccountsJson) && count($wechatAccountsJson) > 0) {
$firstWechat = $wechatAccountsJson[0];
$wechatId = $firstWechat['wechatId'] ?? '';
$nickname = $firstWechat['wechatNickname'] ?? '';
$alias = $firstWechat['alias'] ?? '';
$avatar = $firstWechat['wechatAvatar'] ?? '';
$totalFriend = 0; // JSON 中没有 totalFriend 字段
}
$deviceList[] = [
'id' => $device['id'],
'imei' => $device['imei'] ?? '',
'memo' => $device['memo'] ?? '',
'alive' => $device['alive'] ?? 0,
'wechatId' => $wechatId,
'nickname' => $nickname,
'alias' => $alias,
'avatar' => $avatar,
'totalFriend' => $totalFriend
];
}
}
unset($device);
$workbench->config->deviceGroupsOptions = $deviceList;
} else {
@@ -1071,6 +1195,7 @@ class WorkbenchController extends Controller
$config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE);
$config->startTime = $param['startTime'] ?? '';
$config->endTime = $param['endTime'] ?? '';
$config->executorId = intval($param['executorId'] ?? 3);
$config->groupSizeMin = intval($param['groupSizeMin'] ?? 3);
$config->groupSizeMax = intval($param['groupSizeMax'] ?? 38);
$config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20);
@@ -1321,6 +1446,7 @@ class WorkbenchController extends Controller
$newConfig->devices = $config->devices;
$newConfig->startTime = $config->startTime;
$newConfig->endTime = $config->endTime;
$newConfig->executorId = $config->executorId;
$newConfig->groupSizeMin = $config->groupSizeMin;
$newConfig->groupSizeMax = $config->groupSizeMax;
$newConfig->maxGroupsPerDay = $config->maxGroupsPerDay;
@@ -2206,9 +2332,93 @@ class WorkbenchController extends Controller
*/
public function getGroupPushStats()
{
$controller = new \app\cunkebao\controller\workbench\WorkbenchGroupPushController();
$controller->request = $this->request;
return $controller->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
]
]);
}
/**
@@ -2718,9 +2928,291 @@ class WorkbenchController extends Controller
*/
public function getGroupPushHistory()
{
$controller = new \app\cunkebao\controller\workbench\WorkbenchGroupPushController();
$controller->request = $this->request;
return $controller->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
]
]);
}
/**
@@ -3037,7 +3529,7 @@ class WorkbenchController extends Controller
'isOwner' => $isOwner, // 标记群主
'joinStatus' => $joinStatus, // 入群状态auto=自动建群加入manual=其他方式加入
'isQuit' => $isQuit, // 是否已退群0=在群中1=已退群
'joinTime' => !empty($member['joinTime']) ? date('Y-m-d H:i:s', $member['joinTime']) : '', // 入群时间
'joinTime' => (!empty($member['joinTime']) && is_numeric($member['joinTime']) && $member['joinTime'] > 0) ? date('Y-m-d H:i:s', intval($member['joinTime'])) : '', // 入群时间
];
}
@@ -3066,7 +3558,7 @@ class WorkbenchController extends Controller
'isOwner' => $isOwner, // 标记群主
'joinStatus' => 'auto', // 入群状态auto=自动建群加入
'isQuit' => 1, // 是否已退群1=已退群
'joinTime' => !empty($autoMember['autoJoinTime']) ? date('Y-m-d H:i:s', $autoMember['autoJoinTime']) : '', // 入群时间
'joinTime' => (!empty($autoMember['autoJoinTime']) && is_numeric($autoMember['autoJoinTime']) && $autoMember['autoJoinTime'] > 0) ? date('Y-m-d H:i:s', intval($autoMember['autoJoinTime'])) : '', // 入群时间
];
}
@@ -3078,6 +3570,72 @@ class WorkbenchController extends Controller
return $a['isOwner'] > $b['isOwner'] ? -1 : 1;
});
// 获取工作台配置,检查是否有 executorId
$groupCreateConfig = WorkbenchGroupCreate::where('workbenchId', $workbenchId)->find();
$executorId = !empty($groupCreateConfig) ? intval($groupCreateConfig->executorId ?? 0) : 0;
// 如果 executorId 有值,查询设备详情(格式和 deviceGroupsOptions 一样,但返回一维数组)
$executor = null;
if (!empty($executorId)) {
// 查询设备基本信息
$device = Db::table('s2_device')
->where('id', $executorId)
->where('isDeleted', 0)
->field('id,imei,memo,alive,wechatAccounts')
->find();
if (!empty($device)) {
// 查询关联的微信账号(通过 currentDeviceId
$wechatAccount = Db::table('s2_wechat_account')
->where('currentDeviceId', $executorId)
->field('wechatId,nickname,alias,avatar,totalFriend')
->find();
// 解析 wechatAccounts JSON 字段
$wechatAccountsJson = [];
if (!empty($device['wechatAccounts'])) {
$wechatAccountsJson = json_decode($device['wechatAccounts'], true);
if (!is_array($wechatAccountsJson)) {
$wechatAccountsJson = [];
}
}
// 优先使用 s2_wechat_account 表的数据,如果没有则使用 wechatAccounts JSON 中的第一个
$wechatId = '';
$nickname = '';
$alias = '';
$avatar = '';
$totalFriend = 0;
if (!empty($wechatAccount)) {
$wechatId = $wechatAccount['wechatId'] ?? '';
$nickname = $wechatAccount['nickname'] ?? '';
$alias = $wechatAccount['alias'] ?? '';
$avatar = $wechatAccount['avatar'] ?? '';
$totalFriend = intval($wechatAccount['totalFriend'] ?? 0);
} elseif (!empty($wechatAccountsJson) && is_array($wechatAccountsJson) && count($wechatAccountsJson) > 0) {
$firstWechat = $wechatAccountsJson[0];
$wechatId = $firstWechat['wechatId'] ?? '';
$nickname = $firstWechat['wechatNickname'] ?? '';
$alias = $firstWechat['alias'] ?? '';
$avatar = $firstWechat['wechatAvatar'] ?? '';
$totalFriend = 0; // JSON 中没有 totalFriend 字段
}
$executor = [
'id' => $device['id'],
'imei' => $device['imei'] ?? '',
'memo' => $device['memo'] ?? '',
'alive' => $device['alive'] ?? 0,
'wechatId' => $wechatId,
'nickname' => $nickname,
'alias' => $alias,
'avatar' => $avatar,
'totalFriend' => $totalFriend
];
}
}
// 格式化返回数据
$result = [
'id' => $group['id'],
@@ -3089,11 +3647,12 @@ class WorkbenchController extends Controller
'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"
'createTime' => (!empty($group['createTime']) && is_numeric($group['createTime']) && $group['createTime'] > 0) ? date('Y-m-d H:i', intval($group['createTime'])) : '', // 格式化为"YYYY-MM-DD HH:MM"
'memberCount' => $memberCount,
'memberCountText' => $memberCount . '人', // 格式化为"XX人"
'workbenchName' => $workbench->name ?? '', // 任务名称(工作台名称)
'members' => $members // 所有成员列表
'members' => $members, // 所有成员列表
'executor' => $executor // 执行设备详情(当 executorId 有值时返回,格式和 deviceGroupsOptions 一样)
];
return json([
@@ -3408,9 +3967,112 @@ class WorkbenchController extends Controller
*/
public function quitGroup()
{
$controller = new \app\cunkebao\controller\workbench\WorkbenchGroupCreateController();
$controller->request = $this->request;
return $controller->quitGroup();
$workbenchId = $this->request->param('workbenchId', 0);
$groupId = $this->request->param('groupId', 0);
if (empty($groupId)) {
return json(['code' => 400, 'msg' => '群ID不能为空']);
}
// 查询群基本信息
$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['chatroomId'] ?? '';
if (empty($chatroomId)) {
return json(['code' => 400, 'msg' => '群聊ID不存在']);
}
try {
// 直接使用群表中的账号信息
$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
]);
// 使用与 modifyGroupInfo 相同的方式但操作类型改为4退群
$params = [
"chatroomOperateType" => 4, // 4 表示退群
"cmdType" => "CmdChatroomOperate",
"seq" => time(),
"wechatAccountId" => $executeWechatId,
"wechatChatroomId" => $chatroomId
];
// 使用反射调用 protected 的 sendMessage 方法
$reflection = new \ReflectionClass($webSocketController);
$method = $reflection->getMethod('sendMessage');
$method->setAccessible(true);
$quitResult = $method->invoke($webSocketController, $params, false);
// sendMessage 返回的是数组
if (empty($quitResult) || (isset($quitResult['code']) && $quitResult['code'] != 200)) {
return json(['code' => 500, 'msg' => '退群失败:' . ($quitResult['msg'] ?? '未知错误')]);
}
// 退群成功后更新数据库
// 将群标记为已删除
Db::table('s2_wechat_chatroom')
->where('id', $groupId)
->update([
'isDeleted' => 1,
'deleteTime' => time(),
'updateTime' => time()
]);
// 如果提供了 workbenchId更新工作台建群记录的状态标记为已退群
if (!empty($workbenchId)) {
Db::name('workbench_group_create_item')
->where('workbenchId', $workbenchId)
->where('groupId', $groupId)
->update([
'status' => 5, // 可以定义一个新状态5 = 已退群
'updateTime' => time()
]);
}
return json([
'code' => 200,
'msg' => '退群成功',
'data' => [
'groupId' => $groupId,
'chatroomId' => $chatroomId
]
]);
} catch (\Exception $e) {
\think\facade\Log::error("退群异常。群ID: {$groupId}, 错误: " . $e->getMessage());
return json(['code' => 500, 'msg' => '退群失败:' . $e->getMessage()]);
}
}

View File

@@ -378,32 +378,39 @@ class Adapter implements WeChatServiceInterface
}
if ($passedWeChatId && !empty($task_info['msgConf'])) {
// 更新状态为4已通过并已发消息
Db::name('task_customer')
->where('id', $task['id'])
->update(['status' => 4,'passTime' => time(), 'updateTime' => time()]);
// 记录添加好友奖励如果之前没有记录过status从其他状态变为4时
// 注意如果status已经是2说明已经记录过奖励这里不再重复记录
if ($task['status'] != 2 && !empty($task['channelId'])) {
try {
DistributionRewardService::recordAddFriendReward(
$task['task_id'],
$task['id'],
$task['phone'],
intval($task['channelId'])
);
} catch (\Exception $e) {
// 记录错误但不影响主流程
Log::error('记录添加好友奖励失败:' . $e->getMessage());
}
}
if ($passedWeChatId) {
// 获取好友记录(用于发消息 & 拉群)
$wechatFriendRecord = $this->getWeChatAccoutIdAndFriendIdByWeChatIdAndFriendPhone($passedWeChatId, $task['phone']);
$msgConf = is_string($task_info['msgConf']) ? json_decode($task_info['msgConf'], 1) : $task_info['msgConf'];
$wechatFriendRecord && $this->sendMsgToFriend($wechatFriendRecord['id'], $wechatFriendRecord['wechatAccountId'], $msgConf);
if ($wechatFriendRecord) {
// 1. 如配置了消息则先发送消息并将状态置为4已通过并已发消息
if (!empty($task_info['msgConf'])) {
Db::name('task_customer')
->where('id', $task['id'])
->update(['status' => 4,'passTime' => time(), 'updateTime' => time()]);
// 记录添加好友奖励如果之前没有记录过status从其他状态变为4时
if ($task['status'] != 2 && !empty($task['channelId'])) {
try {
DistributionRewardService::recordAddFriendReward(
$task['task_id'],
$task['id'],
$task['phone'],
intval($task['channelId'])
);
} catch (\Exception $e) {
// 记录错误但不影响主流程
Log::error('记录添加好友奖励失败:' . $e->getMessage());
}
}
$msgConf = is_string($task_info['msgConf']) ? json_decode($task_info['msgConf'], 1) : $task_info['msgConf'];
$this->sendMsgToFriend($wechatFriendRecord['id'], $wechatFriendRecord['wechatAccountId'], $msgConf);
}
// 2. 好友通过后,如配置了拉群,则建群并拉人:通过的好友 + 固定成员
$this->createGroupAfterFriendPass($task_info, $wechatFriendRecord['id'], $wechatFriendRecord['wechatAccountId']);
// 如果没有 msgConf则保持之前更新的状态5已通过未发消息不变
}
} else {
@@ -685,10 +692,90 @@ class Adapter implements WeChatServiceInterface
$task_info['reqConf'] = json_decode($task_info['reqConf'], true);
$task_info['msgConf'] = json_decode($task_info['msgConf'], true);
$task_info['tagConf'] = json_decode($task_info['tagConf'], true);
// 处理拉群固定成员配置JSON 字段)
if (!empty($task_info['groupFixedMembers'])) {
$fixedMembers = json_decode($task_info['groupFixedMembers'], true);
$task_info['groupFixedMembers'] = is_array($fixedMembers) ? $fixedMembers : [];
} else {
$task_info['groupFixedMembers'] = [];
}
}
return $task_info;
}
/**
* 好友通过后,根据任务配置建群并拉人(通过好友 + 固定成员)
* @param array $taskInfo customer_acquisition_task 记录(含 groupInviteEnabled/groupName/groupFixedMembers
* @param int $passedFriendId 通过的好友IDs2_wechat_friend.id
* @param int $wechatAccountId 微信账号ID建群账号
*/
protected function createGroupAfterFriendPass(array $taskInfo, int $passedFriendId, int $wechatAccountId): void
{
// 1. 校验拉群开关与基础配置
if (empty($taskInfo['groupInviteEnabled'])) {
return;
}
$groupName = $taskInfo['groupName'] ?? '';
if ($groupName === '') {
return;
}
$fixedMembers = $taskInfo['groupFixedMembers'] ?? [];
if (!is_array($fixedMembers)) {
$fixedMembers = [];
}
// 2. 过滤出有效的固定成员好友ID数字ID
$fixedFriendIds = [];
foreach ($fixedMembers as $member) {
if (is_numeric($member)) {
$fixedFriendIds[] = intval($member);
}
}
// 包含通过的好友
$friendIds = array_unique(array_merge([$passedFriendId], $fixedFriendIds));
if (empty($friendIds)) {
return;
}
try {
// 3. 初始化 WebSocket参考 sendMsgToFriend / Workbench 群创建逻辑)
$toAccountId = '';
$username = Env::get('api.username', '');
$password = Env::get('api.password', '');
if (!empty($username) || !empty($password)) {
$toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId');
}
if (empty($toAccountId)) {
Log::warning('createGroupAfterFriendPass: toAccountId 为空,跳过建群');
return;
}
$webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]);
// 4. 调用建群接口:群名 = 配置的 groupName成员 = 通过好友 + 固定好友
$createResult = $webSocket->CmdChatroomCreate([
'chatroomName' => $groupName,
'wechatFriendIds' => $friendIds,
'wechatAccountId' => $wechatAccountId,
]);
$createResultData = json_decode($createResult, true);
if (empty($createResultData) || !isset($createResultData['code']) || $createResultData['code'] != 200) {
Log::warning('createGroupAfterFriendPass: 建群失败', [
'taskId' => $taskInfo['id'] ?? 0,
'wechatAccountId' => $wechatAccountId,
'friendIds' => $friendIds,
'result' => $createResult,
]);
}
} catch (\Exception $e) {
Log::error('createGroupAfterFriendPass 异常: ' . $e->getMessage());
}
}
// 检查是否是好友关系
public function checkIfIsWeChatFriendByPhone($wxId = '', $phone = '', $siteTags = '')