From 198e0434b3e36f87dcc57b5a989b310088ff75ca Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Wed, 10 Dec 2025 17:58:08 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BE=A4=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/components/OwnerAdminSelector.tsx | 235 ++++++++++ .../workspace/auto-group/form/index.tsx | 79 ++-- .../mobile/workspace/auto-group/form/types.ts | 19 +- .../command/WorkbenchGroupCreateCommand.php | 1 + .../controller/WorkbenchController.php | 32 +- .../WorkbenchGroupCreateAdminFriendJob.php | 155 +++++++ .../job/WorkbenchGroupCreateJob.php | 431 +++++++++++++----- .../WorkbenchGroupCreateOwnerFriendJob.php | 109 +++++ .../job/WorkbenchGroupCreateRetryJob.php | 179 ++++++++ .../job/WorkbenchGroupCreateVerifyJob.php | 248 ++++++++++ 10 files changed, 1346 insertions(+), 142 deletions(-) create mode 100644 Cunkebao/src/pages/mobile/workspace/auto-group/form/components/OwnerAdminSelector.tsx create mode 100644 Server/application/job/WorkbenchGroupCreateAdminFriendJob.php create mode 100644 Server/application/job/WorkbenchGroupCreateOwnerFriendJob.php create mode 100644 Server/application/job/WorkbenchGroupCreateRetryJob.php create mode 100644 Server/application/job/WorkbenchGroupCreateVerifyJob.php diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/OwnerAdminSelector.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/OwnerAdminSelector.tsx new file mode 100644 index 00000000..b96c73db --- /dev/null +++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/components/OwnerAdminSelector.tsx @@ -0,0 +1,235 @@ +import React, { useImperativeHandle, forwardRef } from "react"; +import { Form, Card, Tabs } from "antd"; +import DeviceSelection from "@/components/DeviceSelection"; +import FriendSelection from "@/components/FriendSelection"; +import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; +import { FriendSelectionItem } from "@/components/FriendSelection/data"; + +interface OwnerAdminSelectorProps { + selectedOwners: DeviceSelectionItem[]; + selectedAdmins: FriendSelectionItem[]; + onNext: (data: { + devices: string[]; + devicesOptions: DeviceSelectionItem[]; + admins: string[]; + adminsOptions: FriendSelectionItem[]; + }) => void; +} + +export interface OwnerAdminSelectorRef { + validate: () => Promise; + getValues: () => any; +} + +const OwnerAdminSelector = forwardRef< + OwnerAdminSelectorRef, + OwnerAdminSelectorProps +>(({ selectedOwners, selectedAdmins, onNext }, ref) => { + const [form] = Form.useForm(); + const [owners, setOwners] = React.useState( + selectedOwners || [] + ); + const [admins, setAdmins] = React.useState( + selectedAdmins || [] + ); + + // 当外部传入的 selectedOwners 或 selectedAdmins 变化时,同步内部状态 + React.useEffect(() => { + setOwners(selectedOwners || []); + }, [selectedOwners]); + + React.useEffect(() => { + setAdmins(selectedAdmins || []); + }, [selectedAdmins]); + + // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + validate: async () => { + // 验证群主和管理员 + if (owners.length === 0) { + form.setFields([ + { + name: "devices", + errors: ["请选择一个群主"], + }, + ]); + return false; + } + if (owners.length > 1) { + form.setFields([ + { + name: "devices", + errors: ["群主只能选择一个设备"], + }, + ]); + return false; + } + if (admins.length === 0) { + form.setFields([ + { + name: "admins", + errors: ["请至少选择一个管理员"], + }, + ]); + return false; + } + // 清除错误 + form.setFields([ + { + name: "devices", + errors: [], + }, + { + name: "admins", + errors: [], + }, + ]); + return true; + }, + getValues: () => { + return { + devices: owners.map(o => o.id.toString()), + admins: admins.map(a => a.id.toString()), + devicesOptions: owners, + adminsOptions: admins, + }; + }, + })); + + // 群主选择(设备选择) + const handleOwnersSelect = (selectedDevices: DeviceSelectionItem[]) => { + const previousOwnerId = owners.length > 0 ? owners[0]?.id : null; + const newOwnerId = selectedDevices.length > 0 ? selectedDevices[0]?.id : null; + + // 当群主改变时,清空已选的管理员(因为筛选条件变了) + const shouldClearAdmins = previousOwnerId !== newOwnerId; + + setOwners(selectedDevices); + const ownerIds = selectedDevices.map(d => d.id.toString()); + form.setFieldValue("devices", ownerIds); + + if (shouldClearAdmins) { + setAdmins([]); + form.setFieldValue("admins", []); + } + + // 通知父组件数据变化 + onNext({ + devices: ownerIds, + devicesOptions: selectedDevices, + admins: shouldClearAdmins ? [] : admins.map(a => a.id.toString()), + adminsOptions: shouldClearAdmins ? [] : admins, + }); + }; + + // 管理员选择 + const handleAdminsSelect = (selectedFriends: FriendSelectionItem[]) => { + setAdmins(selectedFriends); + const adminIds = selectedFriends.map(f => f.id.toString()); + form.setFieldValue("admins", adminIds); + // 通知父组件数据变化 + onNext({ + devices: owners.map(o => o.id.toString()), + devicesOptions: owners, + admins: adminIds, + adminsOptions: selectedFriends, + }); + }; + + const tabItems = [ + { + key: "devices", + label: `群主 (${owners.length})`, + children: ( +
+
+

+ 请选择一个群主(设备),该设备将作为新建群聊的群主 +

+
+ 1 ? "error" : ""} + help={ + owners.length === 0 + ? "请选择一个群主(设备)" + : owners.length > 1 + ? "群主只能选择一个设备" + : "" + } + > + + +
+ ), + }, + { + key: "admins", + label: `管理员 (${admins.length})`, + children: ( +
+
+

+ {owners.length === 0 + ? "请先选择群主(设备),然后选择该设备下的好友作为管理员" + : "请选择管理员,管理员将协助管理新建的群聊(仅显示所选设备下的好友)"} +

+
+ + 0 ? owners.map(d => d.id) : []} + enableDeviceFilter={true} + readonly={owners.length === 0} + /> + +
+ ), + }, + ]; + + return ( + +
item.id.toString()), + admins: (selectedAdmins || []).map(item => item.id.toString()), + }} + > +
+

+ 选择群主和管理员 +

+

+ 请选择一个群主(设备)和管理员(好友),他们将负责管理新建的群聊 +

+
+ + + +
+ ); +}); + +OwnerAdminSelector.displayName = "OwnerAdminSelector"; + +export default OwnerAdminSelector; diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx b/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx index bd38b751..db12abc0 100644 --- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx +++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/index.tsx @@ -7,24 +7,29 @@ import { createAutoGroup, updateAutoGroup, getAutoGroupDetail } from "./api"; import { AutoGroupFormData, StepItem } from "./types"; import StepIndicator from "@/components/StepIndicator"; import BasicSettings, { BasicSettingsRef } from "./components/BasicSettings"; -import DeviceSelector, { DeviceSelectorRef } from "./components/DeviceSelector"; +import OwnerAdminSelector, { + OwnerAdminSelectorRef, +} from "./components/OwnerAdminSelector"; import PoolSelector, { PoolSelectorRef } from "./components/PoolSelector"; import NavCommon from "@/components/NavCommon/index"; import dayjs from "dayjs"; import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; +import { FriendSelectionItem } from "@/components/FriendSelection/data"; import { PoolSelectionItem } from "@/components/PoolSelection/data"; const steps: StepItem[] = [ { id: 1, title: "步骤 1", subtitle: "基础设置" }, - { id: 2, title: "步骤 2", subtitle: "选择设备" }, + { id: 2, title: "步骤 2", subtitle: "选择群主和管理员" }, { id: 3, title: "步骤 3", subtitle: "选择流量池包" }, ]; const defaultForm: AutoGroupFormData = { name: "", type: 4, - deviceGroups: [], // 设备组 - deviceGroupsOptions: [], // 设备组选项 + devices: [], // 群主ID列表 + devicesOptions: [], // 群主选项 + admins: [], // 管理员ID列表 + adminsOptions: [], // 管理员选项 poolGroups: [], // 内容库 poolGroupsOptions: [], // 内容库选项 startTime: dayjs().format("HH:mm"), // 开始时间 (HH:mm) @@ -45,16 +50,15 @@ const AutoGroupForm: React.FC = () => { const [loading, setLoading] = useState(false); const [dataLoaded, setDataLoaded] = useState(!isEdit); // 非编辑模式直接标记为已加载 const [formData, setFormData] = useState(defaultForm); - const [deviceGroupsOptions, setDeviceGroupsOptions] = useState< - DeviceSelectionItem[] - >([]); + const [devicesOptions, setDevicesOptions] = useState([]); + const [adminsOptions, setAdminsOptions] = useState([]); const [poolGroupsOptions, setpoolGroupsOptions] = useState< PoolSelectionItem[] >([]); // 创建子组件的ref const basicSettingsRef = useRef(null); - const deviceSelectorRef = useRef(null); + const ownerAdminSelectorRef = useRef(null); const poolSelectorRef = useRef(null); useEffect(() => { @@ -64,8 +68,10 @@ const AutoGroupForm: React.FC = () => { const updatedForm = { ...defaultForm, name: res.name, - deviceGroups: res.config.deviceGroups || [], - deviceGroupsOptions: res.config.deviceGroupsOptions || [], + devices: res.config.deviceGroups || res.config.devices || [], // 兼容deviceGroups和devices + devicesOptions: res.config.deviceGroupsOptions || res.config.devicesOptions || [], // 兼容deviceGroupsOptions和devicesOptions + admins: res.config.admins || [], + adminsOptions: res.config.adminsOptions || [], poolGroups: res.config.poolGroups || [], poolGroupsOptions: res.config.poolGroupsOptions || [], startTime: res.config.startTime, @@ -80,7 +86,8 @@ const AutoGroupForm: React.FC = () => { id: res.id, }; setFormData(updatedForm); - setDeviceGroupsOptions(res.config.deviceGroupsOptions || []); + setDevicesOptions(res.config.deviceGroupsOptions || res.config.devicesOptions || []); // 兼容deviceGroupsOptions和devicesOptions + setAdminsOptions(res.config.adminsOptions || []); setpoolGroupsOptions(res.config.poolGroupsOptions || []); setDataLoaded(true); // 标记数据已加载 }); @@ -90,16 +97,20 @@ const AutoGroupForm: React.FC = () => { setFormData(prev => ({ ...prev, ...values })); }; - // 设备组选择 - const handleDevicesChange = (data: { - deviceGroups: string[]; - deviceGroupsOptions: DeviceSelectionItem[]; + // 群主和管理员选择 + const handleOwnerAdminChange = (data: { + devices: string[]; + devicesOptions: DeviceSelectionItem[]; + admins: string[]; + adminsOptions: FriendSelectionItem[]; }) => { setFormData(prev => ({ ...prev, - deviceGroups: data.deviceGroups, + devices: data.devices, + admins: data.admins, })); - setDeviceGroupsOptions(data.deviceGroupsOptions); + setDevicesOptions(data.devicesOptions); + setAdminsOptions(data.adminsOptions); }; // 流量池包选择 @@ -116,8 +127,16 @@ const AutoGroupForm: React.FC = () => { Toast.show({ content: "请输入任务名称" }); return; } - if (formData.deviceGroups.length === 0) { - Toast.show({ content: "请选择至少一个设备组" }); + if (formData.devices.length === 0) { + Toast.show({ content: "请选择一个群主" }); + return; + } + if (formData.devices.length > 1) { + Toast.show({ content: "群主只能选择一个设备" }); + return; + } + if (formData.admins.length === 0) { + Toast.show({ content: "请至少选择一个管理员" }); return; } if (formData.poolGroups.length === 0) { @@ -127,9 +146,13 @@ const AutoGroupForm: React.FC = () => { setLoading(true); try { + // 构建提交数据,将devices映射为deviceGroups + const { devices, devicesOptions, ...restFormData } = formData; const submitData = { - ...formData, - deviceGroupsOptions: deviceGroupsOptions, + ...restFormData, + deviceGroups: devices, // 设备ID数组,传输字段名为deviceGroups + deviceGroupsOptions: devicesOptions, // 设备完整信息,传输字段名为deviceGroupsOptions + adminsOptions: adminsOptions, poolGroupsOptions: poolGroupsOptions, }; @@ -173,8 +196,9 @@ const AutoGroupForm: React.FC = () => { break; case 2: - // 调用 DeviceSelector 的表单校验 - isValid = (await deviceSelectorRef.current?.validate()) || false; + // 调用 OwnerAdminSelector 的表单校验 + isValid = + (await ownerAdminSelectorRef.current?.validate()) || false; if (isValid) { setCurrentStep(3); } @@ -217,10 +241,11 @@ const AutoGroupForm: React.FC = () => { ); case 2: return ( - ); case 3: diff --git a/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts b/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts index ba9cbf8a..87c18911 100644 --- a/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts +++ b/Cunkebao/src/pages/mobile/workspace/auto-group/form/types.ts @@ -1,13 +1,16 @@ -import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; import { PoolSelectionItem } from "@/components/PoolSelection/data"; +import { DeviceSelectionItem } from "@/components/DeviceSelection/data"; +import { FriendSelectionItem } from "@/components/FriendSelection/data"; // 自动建群表单数据类型定义 export interface AutoGroupFormData { id?: string; // 任务ID type: number; // 任务类型 name: string; // 任务名称 - deviceGroups: string[]; // 设备组 - deviceGroupsOptions: DeviceSelectionItem[]; // 设备组选项 + devices: string[]; // 群主ID列表(设备ID) + devicesOptions: DeviceSelectionItem[]; // 群主选项(设备) + admins: string[]; // 管理员ID列表(好友ID) + adminsOptions: FriendSelectionItem[]; // 管理员选项(好友) poolGroups: string[]; // 流量池 poolGroupsOptions: PoolSelectionItem[]; // 流量池选项 startTime: string; // 开始时间 (YYYY-MM-DD HH:mm:ss) @@ -34,9 +37,13 @@ export const formValidationRules = { { required: true, message: "请输入任务名称" }, { min: 2, max: 50, message: "任务名称长度应在2-50个字符之间" }, ], - deviceGroups: [ - { required: true, message: "请选择设备组" }, - { type: "array", min: 1, message: "至少选择一个设备组" }, + devices: [ + { required: true, message: "请选择群主" }, + { type: "array", min: 1, max: 1, message: "群主只能选择一个设备" }, + ], + admins: [ + { required: true, message: "请选择管理员" }, + { type: "array", min: 1, message: "至少选择一个管理员" }, ], poolGroups: [ { required: true, message: "请选择内容库" }, diff --git a/Server/application/command/WorkbenchGroupCreateCommand.php b/Server/application/command/WorkbenchGroupCreateCommand.php index df531850..daa70309 100644 --- a/Server/application/command/WorkbenchGroupCreateCommand.php +++ b/Server/application/command/WorkbenchGroupCreateCommand.php @@ -34,6 +34,7 @@ class WorkbenchGroupCreateCommand extends Command // 检查队列是否已经在运行 $queueLockKey = "queue_lock:{$this->queueName}"; + Cache::rm($queueLockKey); if (Cache::get($queueLockKey)) { $output->writeln("队列 {$this->queueName} 已经在运行中,跳过执行"); Log::warning("队列 {$this->queueName} 已经在运行中,跳过执行"); diff --git a/Server/application/cunkebao/controller/WorkbenchController.php b/Server/application/cunkebao/controller/WorkbenchController.php index 823d0629..969eaeed 100644 --- a/Server/application/cunkebao/controller/WorkbenchController.php +++ b/Server/application/cunkebao/controller/WorkbenchController.php @@ -140,6 +140,7 @@ class WorkbenchController extends Controller $config->groupDescription = $param['groupDescription']; $config->poolGroups = json_encode($param['poolGroups'] ?? []); $config->wechatGroups = json_encode($param['wechatGroups'] ?? []); + $config->admins = json_encode($param['admins'] ?? [], JSON_UNESCAPED_UNICODE); $config->createTime = time(); $config->updateTime = time(); $config->save(); @@ -229,7 +230,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'); + $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'); @@ -348,6 +349,18 @@ class WorkbenchController extends Controller $item->config->devices = json_decode($item->config->devices, true); $item->config->poolGroups = json_decode($item->config->poolGroups, true); $item->config->wechatGroups = json_decode($item->config->wechatGroups, true); + $item->config->admins = json_decode($item->config->admins ?? '[]', true) ?: []; + if (!empty($item->config->admins)) { + $adminOptions = Db::table('s2_wechat_friend')->alias('wf') + ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') + ->where('wf.id', 'in', $item->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(); + $item->config->adminsOptions = $adminOptions; + } else { + $item->config->adminsOptions = []; + } } unset($item->groupCreate, $item->group_create); break; @@ -457,7 +470,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'); + $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'); @@ -567,6 +580,7 @@ class WorkbenchController extends Controller $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) ?: []; unset($workbench->groupCreate, $workbench->group_create); } break; @@ -761,6 +775,18 @@ class WorkbenchController extends Controller $workbench->config->ownerWechatOptions = []; } + // 获取管理员选项(自动建群) + 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]); } @@ -882,6 +908,7 @@ class WorkbenchController extends Controller $config->groupDescription = $param['groupDescription']; $config->poolGroups = json_encode($param['poolGroups'] ?? []); $config->wechatGroups = json_encode($param['wechatGroups'] ?? []); + $config->admins = json_encode($param['admins'] ?? [], JSON_UNESCAPED_UNICODE); $config->updateTime = time(); $config->save(); } @@ -1109,6 +1136,7 @@ class WorkbenchController extends Controller $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(); diff --git a/Server/application/job/WorkbenchGroupCreateAdminFriendJob.php b/Server/application/job/WorkbenchGroupCreateAdminFriendJob.php new file mode 100644 index 00000000..459db929 --- /dev/null +++ b/Server/application/job/WorkbenchGroupCreateAdminFriendJob.php @@ -0,0 +1,155 @@ +delete(); + return true; + } + + // 获取管理员信息 + $adminFriends = Db::table('s2_wechat_friend') + ->where('id', 'in', $adminFriendIds) + ->column('id,wechatId,ownerWechatId'); + + if (empty($adminFriends)) { + Log::warning("未找到管理员好友信息。工作台ID: {$workbenchId}"); + $job->delete(); + return true; + } + + // 获取微信账号信息 + $wechatAccount = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->find(); + if (empty($wechatAccount)) { + Log::error("未找到微信账号。微信账号ID: {$wechatAccountId}"); + $job->delete(); + return false; + } + + // 从流量池用户中查找每个管理员的好友 + // 管理员的好友:从s2_wechat_friend表中查找,ownerWechatId=管理员的wechatId,且wechatId在流量池用户中 + $allAdminFriendIds = []; + foreach ($adminFriends as $adminFriend) { + $adminWechatId = $adminFriend['wechatId']; + + // 从好友表中查找该管理员的好友(在流量池用户中) + $adminFriendsList = Db::table('s2_wechat_friend') + ->where('ownerWechatId', $adminWechatId) + ->whereIn('wechatId', $poolUsers) + ->column('id,wechatId'); + + if (!empty($adminFriendsList)) { + $allAdminFriendIds = array_merge($allAdminFriendIds, array_keys($adminFriendsList)); + } + } + + $allAdminFriendIds = array_unique($allAdminFriendIds); + + if (empty($allAdminFriendIds)) { + Log::info("未找到管理员的好友,跳过拉人。工作台ID: {$workbenchId}"); + $job->delete(); + return true; + } + + // 初始化WebSocket + $toAccountId = ''; + $username = Env::get('api.username2', ''); + $password = Env::get('api.password2', ''); + if (!empty($username) || !empty($password)) { + $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); + } + $webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); + + // 拉管理员好友进群 + $inviteResult = $webSocket->CmdChatroomInvite([ + 'wechatChatroomId' => $groupId, + 'wechatFriendIds' => $allAdminFriendIds + ]); + + // 记录管理员好友进群 + $installData = []; + foreach ($allAdminFriendIds as $friendId) { + $friendInfo = Db::table('s2_wechat_friend')->where('id', $friendId)->find(); + $installData[] = [ + 'workbenchId' => $workbenchId, + 'friendId' => $friendId, + 'wechatId' => $friendInfo['wechatId'] ?? '', + 'groupId' => $groupId, + 'wechatAccountId' => $wechatAccountId, + 'status' => self::STATUS_ADMIN_FRIEND_ADDED, + 'memberType' => self::MEMBER_TYPE_ADMIN_FRIEND, + 'retryCount' => 0, + 'chatroomId' => $chatroomId, + 'createTime' => time(), + ]; + } + + if (!empty($installData)) { + Db::name('workbench_group_create_item')->insertAll($installData); + Log::info("管理员好友已拉入群。工作台ID: {$workbenchId}, 群ID: {$groupId}, 好友数: " . count($installData)); + } + + $job->delete(); + return true; + } catch (\Exception $e) { + Log::error("拉管理员好友任务异常:{$e->getMessage()}"); + + if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) { + $job->delete(); + } else { + $job->release(Config::get('queue.failed_delay', 10)); + } + + return false; + } + } +} + diff --git a/Server/application/job/WorkbenchGroupCreateJob.php b/Server/application/job/WorkbenchGroupCreateJob.php index 197134c2..6225a4e4 100644 --- a/Server/application/job/WorkbenchGroupCreateJob.php +++ b/Server/application/job/WorkbenchGroupCreateJob.php @@ -7,6 +7,7 @@ use app\cunkebao\model\Workbench; use app\cunkebao\model\WorkbenchGroupCreate; use app\api\model\WechatFriendModel as WechatFriend; use app\api\model\WechatMomentsModel as WechatMoments; +use app\common\model\DeviceWechatLogin as DeviceWechatLoginModel; use think\facade\Log; use think\facade\Env; use think\Db; @@ -16,6 +17,7 @@ use think\facade\Config; use app\api\controller\MomentsController as Moments; use Workerman\Lib\Timer; use app\api\controller\WechatController; +use think\Queue; /** * 工作台群创建任务 @@ -50,6 +52,23 @@ class WorkbenchGroupCreateJob } } + /** + * 成员类型常量 + */ + const MEMBER_TYPE_OWNER = 1; // 群主成员 + const MEMBER_TYPE_ADMIN = 2; // 管理员 + const MEMBER_TYPE_OWNER_FRIEND = 3; // 群主好友 + const MEMBER_TYPE_ADMIN_FRIEND = 4; // 管理员好友 + + /** + * 状态常量 + */ + const STATUS_PENDING = 0; // 待创建 + const STATUS_CREATING = 1; // 创建中 + const STATUS_SUCCESS = 2; // 创建成功 + const STATUS_FAILED = 3; // 创建失败 + const STATUS_ADMIN_FRIEND_ADDED = 4; // 管理员好友已拉入 + /** * 执行任务 * @throws \Exception @@ -57,7 +76,7 @@ class WorkbenchGroupCreateJob public function execute() { try { - // 获取所有工作台 + // 1. 查询启用了建群功能的数据 $workbenches = Workbench::where(['status' => 1, 'type' => 4, 'isDel' => 0])->order('id desc')->select(); foreach ($workbenches as $workbench) { // 获取工作台配置 @@ -65,158 +84,356 @@ class WorkbenchGroupCreateJob if (!$config) { continue; } + + // 解析配置 $config['poolGroups'] = json_decode($config['poolGroups'], true); $config['devices'] = json_decode($config['devices'], true); + $config['admins'] = json_decode($config['admins'] ?? '[]', true) ?: []; + if (empty($config['poolGroups']) || empty($config['devices'])) { continue; } - //群主及内部成员 - $groupMember = Db::name('device_wechat_login')->alias('dwl') - ->join(['s2_wechat_account' => 'a'], 'dwl.wechatId = a.wechatId') - ->whereIn('dwl.deviceId', $config['devices']) - ->group('a.id') - ->column('a.wechatId'); - if (empty($groupMember)) { + $groupMember = []; + $wechatId = Db::name('device_wechat_login') + ->whereIn('deviceId',$config['devices']) + ->order('id desc') + ->value('wechatId'); + if (empty($wechatId)) { continue; } - $groupMemberWechatId = Db::table('s2_wechat_friend') - ->where('ownerWechatId', $groupMember[0]) - ->whereIn('wechatId', $groupMember) - ->column('id,wechatId'); + $groupMember[] = $wechatId; + // 获取群主好友ID映射(所有群主的好友) + $groupMemberWechatId = []; + $groupMemberId = []; + + foreach ($groupMember as $ownerWechatId) { + $friends = Db::table('s2_wechat_friend') + ->where('ownerWechatId', $ownerWechatId) + ->whereIn('wechatId', $groupMember) + ->field('id,wechatId') + ->select(); + + foreach ($friends as $friend) { + if (!isset($groupMemberWechatId[$friend['id']])) { + $groupMemberWechatId[$friend['id']] = $friend['wechatId']; + $groupMemberId[] = $friend['id']; + } + } + } + if (empty($groupMemberWechatId)) { continue; } - $groupMemberId = array_keys($groupMemberWechatId); - - //流量池用户 + // 获取流量池用户 $poolItem = Db::name('traffic_source_package_item') ->whereIn('packageId', $config['poolGroups']) ->group('identifier') ->column('identifier'); + if (empty($poolItem)) { continue; } - //群用户 + // 获取已入群的用户(排除已成功入群的) $groupUser = Db::name('workbench_group_create_item') ->where('workbenchId', $workbench->id) + ->where('status', 'in', [self::STATUS_SUCCESS, self::STATUS_ADMIN_FRIEND_ADDED]) ->whereIn('wechatId', $poolItem) ->group('wechatId') ->column('wechatId'); - - //待入群的用户 + // 待入群的用户 $joinUser = array_diff($poolItem, $groupUser); if (empty($joinUser)) { continue; } - - //随机群人数 + // 计算随机群人数(不包含管理员,只减去群主成员数) $groupRandNum = mt_rand($config['groupSizeMin'], $config['groupSizeMax']) - count($groupMember); - //待加入用户 + // 分批处理待入群用户 $addGroupUser = []; $totalRows = count($joinUser); for ($i = 0; $i < $totalRows; $i += $groupRandNum) { $batchRows = array_slice($joinUser, $i, $groupRandNum); if (!empty($batchRows)) { - $user = []; - foreach ($batchRows as $row) { - $user[] = $row; - } - $addGroupUser[] = $user; + $addGroupUser[] = $batchRows; } } - foreach ($addGroupUser as $key => $val) { - //判断第一组用户是否满足创建群的条件 - $friendIds = Db::name('wechat_friendship')->alias('f') - ->join(['s2_wechat_account' => 'a'], 'f.ownerWechatId=a.wechatId') - ->where('f.companyId', $workbench->companyId) - ->whereIn('f.wechatId', $val) - ->group('f.wechatId') - ->column('f.id,f.wechatId,a.id as wechatAccountId'); + // 初始化WebSocket + $toAccountId = ''; + $username = Env::get('api.username2', ''); + $password = Env::get('api.password2', ''); + if (!empty($username) || !empty($password)) { + $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); + } + $webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); - // 整理数组:按wechatAccountId分组,值为对应的id数组 - $groupedFriends = []; - $wechatAccountIds = []; - $wechatIds = []; - foreach ($friendIds as $friend) { - $wechatAccountId = $friend['wechatAccountId']; - if (!in_array($wechatAccountId, $wechatAccountIds)) { - $wechatAccountIds[] = $wechatAccountId; - } - $friendId = $friend['id']; - if (!isset($groupedFriends[$wechatAccountId])) { - $groupedFriends[$wechatAccountId] = []; - } - $groupedFriends[$wechatAccountId][] = $friendId; - $wechatIds[$friendId] = $friend['wechatId']; - } - //==================== 群相关功能开始 =========================== - $toAccountId = ''; - $username = Env::get('api.username2', ''); - $password = Env::get('api.password2', ''); - if (!empty($username) || !empty($password)) { - $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); - } - $webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); - //$webSocket = new WebSocketController(['userName' => 'wz_03', 'password' => 'key123456', 'accountId' => 5015]); - //拉人进群 $webSocket->CmdChatroomInvite(['wechatChatroomId' => 830794, 'wechatFriendIds' => [21168549]]); - //修改群名称 $webSocket->CmdChatroomModifyInfo(['wechatChatroomId' => 830794, 'wechatAccountId' => 300745,'chatroomName' => 'test111']); - //修改群公告 $webSocket->CmdChatroomModifyInfo(['wechatChatroomId' => 830794, 'wechatAccountId' => 300745,'announce' => 'test111']); - //建群 $webSocket->CmdChatroomCreate(['chatroomName' => '聊天测试群', 'wechatFriendIds' => [17453051,17453058],'wechatAccountId' => 300745]); - foreach ($groupedFriends as $wechatAccountId => $friendId) { - //列出所有群 - $group = ''; - $groupMemberNum = 0; - $groupIds = Db::name('workbench_group_create_item')->where(['workbenchId' => $workbench->id])->group('groupId')->column('groupId'); - if (!empty($groupIds)) { - //最新创建的群 - $group = Db::name('wechat_group')->where(['wechatAccountId' => $wechatAccountId])->whereIn('id', $groupIds)->order('createTime DESC')->find(); - //群用户数量 - if (!empty($group)) { - $groupMemberNum = Db::name('wechat_group_member')->where('groupId', $group['id'])->count(); - } - } - - //拉群或者建群 - $wechatFriendIds = array_merge($friendId, $groupMemberId); - - if ($groupMemberNum == 0 || (count($wechatFriendIds) + $groupMemberNum) >= $groupRandNum) { - if (count($groupIds) > 0) { - $chatroomName = $config['groupNameTemplate'] . count($groupIds) + 1 . '群'; - } else { - $chatroomName = $config['groupNameTemplate']; - } - $webSocket->CmdChatroomCreate(['chatroomName' => $chatroomName, 'wechatFriendIds' => $wechatFriendIds,'wechatAccountId' => $wechatAccountId]); - } else { - $webSocket->CmdChatroomInvite(['wechatChatroomId' => $group['id'], 'wechatFriendIds' => $wechatFriendIds]); - } - - $installData = []; - - //记录进群人员 - foreach ($wechatFriendIds as $v) { - $installData[] = [ - 'workbenchId' => $workbench->id, - 'friendId' => $v, - 'wechatId' => !empty($wechatIds[$v]) ? $wechatIds[$v] : $groupMemberWechatId[$v], - 'groupId' => 0, - 'wechatAccountId' => $wechatAccountId, - 'createTime' => time(), - ]; - } - Db::name('workbench_group_create_item')->insertAll($installData); - } + // 遍历每批用户 + foreach ($addGroupUser as $batchUsers) { + $this->processBatchUsers($workbench, $config, $batchUsers, $groupMemberId, $groupMemberWechatId, $groupRandNum, $webSocket); } } } catch (\Exception $e) { - Log::error("消息群发任务异常: " . $e->getMessage()); + Log::error("工作台建群任务异常: " . $e->getMessage()); throw $e; } } + /** + * 处理一批用户 + * @param Workbench $workbench 工作台 + * @param array $config 配置 + * @param array $batchUsers 批次用户(微信ID数组,来自流量池) + * @param array $groupMemberId 群主成员ID数组 + * @param array $groupMemberWechatId 群主成员微信ID映射 + * @param int $groupRandNum 随机群人数(不包含管理员) + * @param WebSocketController $webSocket WebSocket实例 + */ + protected function processBatchUsers($workbench, $config, $batchUsers, $groupMemberId, $groupMemberWechatId, $groupRandNum, $webSocket) + { + // 1. 获取群主微信ID列表(用于验证管理员) + // 从群主成员的好友记录中提取所有群主的微信ID(ownerWechatId) + $groupOwnerWechatIds = []; + foreach ($groupMemberId as $memberId) { + $member = Db::table('s2_wechat_friend')->where('id', $memberId)->find(); + if ($member && !in_array($member['ownerWechatId'], $groupOwnerWechatIds)) { + $groupOwnerWechatIds[] = $member['ownerWechatId']; + } + } + + + // 如果从好友表获取不到,使用群主成员微信ID列表(作为备用) + if (empty($groupOwnerWechatIds)) { + $groupOwnerWechatIds = array_values(array_unique($groupMemberWechatId)); + } + // 2. 验证并获取管理员好友ID(管理员必须是群主的好友) + $adminFriendIds = []; + $adminWechatIds = []; + if (!empty($config['admins'])) { + $adminFriends = Db::table('s2_wechat_friend') + ->where('id', 'in', $config['admins']) + ->field('id,wechatId,ownerWechatId') + ->select(); + + foreach ($adminFriends as $adminFriend) { + // 验证:管理员必须是群主的好友 + if (in_array($adminFriend['ownerWechatId'], $groupOwnerWechatIds)) { + $adminFriendIds[] = $adminFriend['id']; + $adminWechatIds[$adminFriend['id']] = $adminFriend['wechatId']; + } + } + } + + exit_data($adminWechatIds); + // 3. 从流量池用户中筛选出是群主好友的用户(按微信账号分组) + $ownerFriendIdsByAccount = []; + $wechatIds = []; + + // 获取群主的好友关系(从流量池中筛选) + $ownerFriends = Db::name('wechat_friendship')->alias('f') + ->join(['s2_wechat_account' => 'a'], 'f.ownerWechatId=a.wechatId') + ->where('f.companyId', $workbench->companyId) + ->whereIn('f.wechatId', $batchUsers) + ->whereIn('f.ownerWechatId', $groupOwnerWechatIds) + ->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']; + } + + // 4. 遍历每个微信账号,创建群 + foreach ($ownerFriendIdsByAccount as $wechatAccountId => $ownerFriendIds) { + // 4.1 获取当前账号的管理员好友ID + $currentAdminFriendIds = []; + $accountWechatId = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->value('wechatId'); + + foreach ($adminFriendIds as $adminFriendId) { + $adminFriend = Db::table('s2_wechat_friend')->where('id', $adminFriendId)->find(); + if ($adminFriend && $adminFriend['ownerWechatId'] == $accountWechatId) { + $currentAdminFriendIds[] = $adminFriendId; + $wechatIds[$adminFriendId] = $adminWechatIds[$adminFriendId]; + } + } + + // 4.2 获取当前账号的群主成员ID + $currentGroupMemberIds = []; + foreach ($groupMemberId as $memberId) { + $member = Db::table('s2_wechat_friend')->where('id', $memberId)->find(); + if ($member && $member['ownerWechatId'] == $accountWechatId) { + $currentGroupMemberIds[] = $memberId; + if (!isset($wechatIds[$memberId])) { + $wechatIds[$memberId] = $groupMemberWechatId[$memberId] ?? ''; + } + } + } + + // 4.3 限制群主好友数量(按随机群人数) + $limitedOwnerFriendIds = array_slice($ownerFriendIds, 0, $groupRandNum); + + // 4.4 创建群:管理员 + 群主成员 + 群主好友(从流量池筛选) + $createFriendIds = array_merge($currentAdminFriendIds, $currentGroupMemberIds, $limitedOwnerFriendIds); + + if (count($createFriendIds) < 2) { + Log::warning("建群好友数量不足,跳过。工作台ID: {$workbench->id}, 微信账号ID: {$wechatAccountId}"); + continue; + } + + // 4.5 生成群名称 + $existingGroupCount = Db::name('workbench_group_create_item') + ->where('workbenchId', $workbench->id) + ->where('wechatAccountId', $wechatAccountId) + ->where('status', self::STATUS_SUCCESS) + ->group('groupId') + ->count(); + + $chatroomName = $existingGroupCount > 0 + ? $config['groupNameTemplate'] . ($existingGroupCount + 1) . '群' + : $config['groupNameTemplate']; + + // 4.6 调用建群接口 + $createTime = time(); + $createResult = $webSocket->CmdChatroomCreate([ + 'chatroomName' => $chatroomName, + 'wechatFriendIds' => $createFriendIds, + 'wechatAccountId' => $wechatAccountId + ]); + + $createResultData = json_decode($createResult, true); + + // 4.7 解析建群结果,获取群ID + $chatroomId = 0; + if (!empty($createResultData) && isset($createResultData['code']) && $createResultData['code'] == 200) { + // 尝试从返回数据中获取群ID(根据实际API返回格式调整) + if (isset($createResultData['data']['chatroomId'])) { + $chatroomId = $createResultData['data']['chatroomId']; + } elseif (isset($createResultData['data']['id'])) { + $chatroomId = $createResultData['data']['id']; + } + } + + // 4.8 记录创建请求 + $installData = []; + foreach ($createFriendIds as $friendId) { + $memberType = in_array($friendId, $currentAdminFriendIds) + ? self::MEMBER_TYPE_ADMIN + : (in_array($friendId, $currentGroupMemberIds) ? self::MEMBER_TYPE_OWNER : self::MEMBER_TYPE_OWNER_FRIEND); + + $installData[] = [ + 'workbenchId' => $workbench->id, + 'friendId' => $friendId, + 'wechatId' => $wechatIds[$friendId] ?? ($groupMemberWechatId[$friendId] ?? ''), + 'groupId' => $chatroomId, + 'wechatAccountId' => $wechatAccountId, + 'status' => $chatroomId > 0 ? self::STATUS_SUCCESS : self::STATUS_CREATING, + 'memberType' => $memberType, + 'retryCount' => 0, + 'chatroomId' => $chatroomId > 0 ? $chatroomId : null, + '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); + } + } + } + + /** + * 拉管理员的好友进群 + * @param Workbench $workbench 工作台 + * @param array $config 配置 + * @param array $batchUsers 批次用户(流量池微信ID数组) + * @param array $adminFriendIds 管理员好友ID数组 + * @param int $chatroomId 群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) + { + // 获取管理员的微信ID列表 + $adminWechatIds = []; + foreach ($adminFriendIds as $adminFriendId) { + if (isset($wechatIds[$adminFriendId])) { + $adminWechatIds[] = $wechatIds[$adminFriendId]; + } + } + + if (empty($adminWechatIds)) { + return; + } + + // 从流量池用户中筛选出是管理员好友的用户 + $adminFriendsFromPool = Db::name('wechat_friendship')->alias('f') + ->join(['s2_wechat_account' => 'a'], 'f.ownerWechatId=a.wechatId') + ->where('f.companyId', $workbench->companyId) + ->whereIn('f.wechatId', $batchUsers) + ->whereIn('f.ownerWechatId', $adminWechatIds) + ->where('a.id', $wechatAccountId) + ->field('f.id,f.wechatId') + ->select(); + + if (empty($adminFriendsFromPool)) { + Log::info("未找到管理员的好友,跳过拉人。工作台ID: {$workbench->id}, 群ID: {$chatroomId}"); + return; + } + + // 提取好友ID列表 + $adminFriendIdsToInvite = []; + foreach ($adminFriendsFromPool as $friend) { + $adminFriendIdsToInvite[] = $friend['id']; + $wechatIds[$friend['id']] = $friend['wechatId']; + } + + // 调用拉人接口 + $inviteResult = $webSocket->CmdChatroomInvite([ + 'wechatChatroomId' => $chatroomId, + 'wechatFriendIds' => $adminFriendIdsToInvite + ]); + + $inviteResultData = json_decode($inviteResult, true); + $inviteSuccess = !empty($inviteResultData) && isset($inviteResultData['code']) && $inviteResultData['code'] == 200; + + // 记录管理员好友拉入状态 + $adminFriendData = []; + foreach ($adminFriendIdsToInvite as $friendId) { + $adminFriendData[] = [ + 'workbenchId' => $workbench->id, + 'friendId' => $friendId, + 'wechatId' => $wechatIds[$friendId] ?? '', + 'groupId' => $chatroomId, + 'wechatAccountId' => $wechatAccountId, + 'status' => $inviteSuccess ? self::STATUS_ADMIN_FRIEND_ADDED : self::STATUS_FAILED, + 'memberType' => self::MEMBER_TYPE_ADMIN_FRIEND, + 'retryCount' => 0, + 'chatroomId' => $chatroomId, + 'createTime' => $createTime, + ]; + } + Db::name('workbench_group_create_item')->insertAll($adminFriendData); + + if ($inviteSuccess) { + Log::info("管理员好友拉入成功。工作台ID: {$workbench->id}, 群ID: {$chatroomId}, 拉入数量: " . count($adminFriendIdsToInvite)); + } else { + Log::warning("管理员好友拉入失败。工作台ID: {$workbench->id}, 群ID: {$chatroomId}"); + } + } + /** * 获取设备列表 diff --git a/Server/application/job/WorkbenchGroupCreateOwnerFriendJob.php b/Server/application/job/WorkbenchGroupCreateOwnerFriendJob.php new file mode 100644 index 00000000..64097201 --- /dev/null +++ b/Server/application/job/WorkbenchGroupCreateOwnerFriendJob.php @@ -0,0 +1,109 @@ +delete(); + return true; + } + + // 初始化WebSocket + $toAccountId = ''; + $username = Env::get('api.username2', ''); + $password = Env::get('api.password2', ''); + if (!empty($username) || !empty($password)) { + $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); + } + $webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); + + // 拉群主好友进群 + $inviteResult = $webSocket->CmdChatroomInvite([ + 'wechatChatroomId' => $groupId, + 'wechatFriendIds' => $ownerFriendIds + ]); + + // 获取好友微信ID映射 + $friendWechatIds = Db::table('s2_wechat_friend') + ->where('id', 'in', $ownerFriendIds) + ->column('id,wechatId'); + + // 更新群主好友记录状态 + Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('status', 1) // 创建中 + ->where('memberType', self::MEMBER_TYPE_OWNER_FRIEND) + ->where('createTime', '>=', $createTime - 10) + ->where('createTime', '<=', $createTime + 10) + ->update([ + 'status' => self::STATUS_SUCCESS, + 'groupId' => $groupId, + 'chatroomId' => $chatroomId, + 'verifyTime' => time() + ]); + + Log::info("群主好友已拉入群。工作台ID: {$workbenchId}, 群ID: {$groupId}, 好友数: " . count($ownerFriendIds)); + + $job->delete(); + return true; + } catch (\Exception $e) { + Log::error("拉群主好友任务异常:{$e->getMessage()}"); + + if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) { + $job->delete(); + } else { + $job->release(Config::get('queue.failed_delay', 10)); + } + + return false; + } + } +} + diff --git a/Server/application/job/WorkbenchGroupCreateRetryJob.php b/Server/application/job/WorkbenchGroupCreateRetryJob.php new file mode 100644 index 00000000..8f4ab9c7 --- /dev/null +++ b/Server/application/job/WorkbenchGroupCreateRetryJob.php @@ -0,0 +1,179 @@ +find(); + if (!$workbench) { + Log::error("未找到工作台。工作台ID: {$workbenchId}"); + $job->delete(); + return false; + } + + $config = WorkbenchGroupCreate::where('workbenchId', $workbench->id)->find(); + if (!$config) { + Log::error("未找到工作台配置。工作台ID: {$workbenchId}"); + $job->delete(); + return false; + } + + // 获取失败记录 + $failedItems = Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('createTime', '>=', $createTime - 10) + ->where('createTime', '<=', $createTime + 10) + ->where('status', 'in', [1, 3]) // 创建中或失败 + ->select(); + + if (empty($failedItems)) { + Log::info("未找到需要重试的记录。工作台ID: {$workbenchId}"); + $job->delete(); + return true; + } + + // 解析配置 + $config['poolGroups'] = json_decode($config['poolGroups'], true); + $config['devices'] = json_decode($config['devices'], true); + $config['admins'] = json_decode($config['admins'] ?? '[]', true) ?: []; + + // 获取群主成员 + $groupMember = Db::name('device_wechat_login')->alias('dwl') + ->join(['s2_wechat_account' => 'a'], 'dwl.wechatId = a.wechatId') + ->whereIn('dwl.deviceId', $config['devices']) + ->group('a.id') + ->column('a.wechatId'); + + $groupMemberWechatId = Db::table('s2_wechat_friend') + ->where('ownerWechatId', $groupMember[0]) + ->whereIn('wechatId', $groupMember) + ->column('id,wechatId'); + + $groupMemberId = array_keys($groupMemberWechatId); + + // 获取管理员好友ID + $adminFriendIds = []; + if (!empty($config['admins'])) { + $adminFriends = Db::table('s2_wechat_friend') + ->where('id', 'in', $config['admins']) + ->column('id,wechatId,ownerWechatId'); + + $accountWechatId = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->value('wechatId'); + foreach ($adminFriends as $adminFriend) { + if ($adminFriend['ownerWechatId'] == $accountWechatId) { + $adminFriendIds[] = $adminFriend['id']; + } + } + } + + // 初始化WebSocket + $toAccountId = ''; + $username = Env::get('api.username2', ''); + $password = Env::get('api.password2', ''); + if (!empty($username) || !empty($password)) { + $toAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); + } + $webSocket = new WebSocketController(['userName' => $username, 'password' => $password, 'accountId' => $toAccountId]); + + // 重新创建群 + $createFriendIds = array_merge($adminFriendIds, $groupMemberId); + + if (count($createFriendIds) < 2) { + Log::error("重试建群好友数量不足。工作台ID: {$workbenchId}"); + $job->delete(); + return false; + } + + // 生成群名称 + $existingGroupCount = Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('status', 2) // 成功 + ->group('groupId') + ->count(); + + $chatroomName = $existingGroupCount > 0 + ? $config['groupNameTemplate'] . ($existingGroupCount + 1) . '群' + : $config['groupNameTemplate']; + + // 调用建群接口 + $createResult = $webSocket->CmdChatroomCreate([ + 'chatroomName' => $chatroomName, + 'wechatFriendIds' => $createFriendIds, + 'wechatAccountId' => $wechatAccountId + ]); + + // 更新记录状态为创建中 + Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('createTime', '>=', $createTime - 10) + ->where('createTime', '<=', $createTime + 10) + ->update([ + 'status' => 1, // 创建中 + 'createTime' => time() // 更新创建时间 + ]); + + // 创建新的轮询验证任务 + Queue::later(5, 'app\job\WorkbenchGroupCreateVerifyJob', [ + 'workbenchId' => $workbenchId, + 'wechatAccountId' => $wechatAccountId, + 'createTime' => time(), + 'adminFriendIds' => $adminFriendIds, + 'poolUsers' => [], // 重试时暂时不传poolUsers,后续可以优化 + ], 'default'); + + Log::info("重试建群任务已创建。工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}"); + + $job->delete(); + return true; + } catch (\Exception $e) { + Log::error("重试建群任务异常:{$e->getMessage()}"); + + if ($job->attempts() > self::MAX_RETRY_ATTEMPTS) { + $job->delete(); + } else { + $job->release(Config::get('queue.failed_delay', 10)); + } + + return false; + } + } +} + diff --git a/Server/application/job/WorkbenchGroupCreateVerifyJob.php b/Server/application/job/WorkbenchGroupCreateVerifyJob.php new file mode 100644 index 00000000..99c26946 --- /dev/null +++ b/Server/application/job/WorkbenchGroupCreateVerifyJob.php @@ -0,0 +1,248 @@ +attempts(); + + // 查询待验证的群记录 + $groupItems = Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('status', self::STATUS_CREATING) + ->where('createTime', '>=', $createTime - 10) // 允许10秒误差 + ->where('createTime', '<=', $createTime + 10) + ->group('wechatAccountId') + ->select(); + + if (empty($groupItems)) { + Log::info("未找到待验证的群记录,任务完成。工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}"); + $job->delete(); + return true; + } + + // 获取微信账号信息 + $wechatAccount = Db::table('s2_wechat_account')->where('id', $wechatAccountId)->find(); + if (empty($wechatAccount)) { + Log::error("未找到微信账号,任务失败。微信账号ID: {$wechatAccountId}"); + $job->delete(); + return false; + } + + // 调用接口查询群聊列表 + $chatroomController = new WechatChatroomController(); + $chatroomList = $chatroomController->getlist([ + 'wechatAccountKeyword' => $wechatAccount['wechatId'], + 'pageIndex' => 0, + 'pageSize' => 100 + ], true); + + $chatroomListData = json_decode($chatroomList, true); + + if (empty($chatroomListData['data']['results'])) { + // 如果超过最大重试次数,标记为失败并重试创建 + if ($attempts >= self::MAX_RETRY_ATTEMPTS) { + $this->handleCreateFailed($workbenchId, $wechatAccountId, $createTime, $job); + return false; + } + + // 继续轮询 + $job->release(self::POLL_INTERVAL); + return false; + } + + // 查找符合条件的群(chatroomOwnerAvatar和chatroomOwnerNickname不为空) + $successGroup = null; + foreach ($chatroomListData['data']['results'] as $chatroom) { + if (!empty($chatroom['chatroomOwnerAvatar']) && !empty($chatroom['chatroomOwnerNickname'])) { + // 检查创建时间是否匹配(允许30秒误差) + $chatroomCreateTime = isset($chatroom['createTime']) ? strtotime($chatroom['createTime']) : 0; + if (abs($chatroomCreateTime - $createTime) <= 30) { + $successGroup = $chatroom; + break; + } + } + } + + if ($successGroup) { + // 群创建成功,更新记录状态 + $groupId = $successGroup['id'] ?? 0; + $chatroomId = $successGroup['chatroomId'] ?? ''; + + // 更新管理员和群主成员的记录状态 + Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('status', self::STATUS_CREATING) + ->where('memberType', 'in', [1, 2]) // 群主成员和管理员 + ->where('createTime', '>=', $createTime - 10) + ->where('createTime', '<=', $createTime + 10) + ->update([ + 'status' => self::STATUS_SUCCESS, + 'groupId' => $groupId, + 'chatroomId' => $chatroomId, + 'verifyTime' => time() + ]); + + Log::info("群创建成功!工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}, 群ID: {$groupId}"); + + // 3. 拉群主好友进群(在验证成功后执行) + $ownerFriendIds = $data['ownerFriendIds'] ?? []; + if (!empty($ownerFriendIds)) { + Queue::push('app\job\WorkbenchGroupCreateOwnerFriendJob', [ + 'workbenchId' => $workbenchId, + 'wechatAccountId' => $wechatAccountId, + 'groupId' => $groupId, + 'chatroomId' => $chatroomId, + 'ownerFriendIds' => $ownerFriendIds, + 'createTime' => $createTime + ], 'default'); + } + + // 5. 创建拉管理员好友的任务(在群主好友拉入后执行) + if (!empty($adminFriendIds) && !empty($poolUsers)) { + Queue::push('app\job\WorkbenchGroupCreateAdminFriendJob', [ + 'workbenchId' => $workbenchId, + 'wechatAccountId' => $wechatAccountId, + 'groupId' => $groupId, + 'chatroomId' => $chatroomId, + 'adminFriendIds' => $adminFriendIds, + 'poolUsers' => $poolUsers + ], 'default'); + } + + $job->delete(); + return true; + } else { + // 如果超过最大重试次数,标记为失败并重试创建 + if ($attempts >= self::MAX_RETRY_ATTEMPTS) { + $this->handleCreateFailed($workbenchId, $wechatAccountId, $createTime, $job); + return false; + } + + // 继续轮询 + $job->release(self::POLL_INTERVAL); + return false; + } + } catch (\Exception $e) { + Log::error("群创建验证任务异常:{$e->getMessage()}"); + + if ($job->attempts() >= self::MAX_RETRY_ATTEMPTS) { + $job->delete(); + } else { + $job->release(self::POLL_INTERVAL); + } + + return false; + } + } + + /** + * 处理创建失败的情况(重试创建) + * @param int $workbenchId 工作台ID + * @param int $wechatAccountId 微信账号ID + * @param int $createTime 创建时间 + * @param Job $job 队列任务 + */ + protected function handleCreateFailed($workbenchId, $wechatAccountId, $createTime, $job) + { + // 更新状态为失败 + Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('status', self::STATUS_CREATING) + ->where('createTime', '>=', $createTime - 10) + ->where('createTime', '<=', $createTime + 10) + ->update([ + 'status' => self::STATUS_FAILED, + 'verifyTime' => time() + ]); + + Log::warning("群创建失败,准备重试。工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}"); + + // 检查重试次数 + $failedItems = Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('createTime', '>=', $createTime - 10) + ->where('createTime', '<=', $createTime + 10) + ->select(); + + $maxRetryCount = 0; + foreach ($failedItems as $item) { + if ($item['retryCount'] >= 3) { + Log::error("群创建重试次数已达上限,放弃重试。工作台ID: {$workbenchId}, 微信账号ID: {$wechatAccountId}"); + $job->delete(); + return; + } + $maxRetryCount = max($maxRetryCount, $item['retryCount']); + } + + // 增加重试次数并重置状态 + Db::name('workbench_group_create_item') + ->where('workbenchId', $workbenchId) + ->where('wechatAccountId', $wechatAccountId) + ->where('createTime', '>=', $createTime - 10) + ->where('createTime', '<=', $createTime + 10) + ->update([ + 'status' => self::STATUS_CREATING, + 'retryCount' => Db::raw('retryCount + 1') + ]); + + // 重新创建建群任务(延迟10秒) + Queue::later(10, 'app\job\WorkbenchGroupCreateRetryJob', [ + 'workbenchId' => $workbenchId, + 'wechatAccountId' => $wechatAccountId, + 'createTime' => $createTime + ], 'default'); + + $job->delete(); + } +} +