diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts index a6e5bade..e88f7fcb 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts +++ b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.data.ts @@ -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: [], diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx index 87b2dfab..1a284aee 100644 --- a/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx +++ b/Cunkebao/src/pages/mobile/scenarios/plan/new/index.tsx @@ -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 ; + case 5: return ; default: return null; diff --git a/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/GroupSettings.tsx b/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/GroupSettings.tsx new file mode 100644 index 00000000..780cf50b --- /dev/null +++ b/Cunkebao/src/pages/mobile/scenarios/plan/new/steps/GroupSettings.tsx @@ -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 = ({ formData, onChange }) => { + const handleToggleGroupInvite = (value: boolean) => { + onChange({ + ...formData, + groupInviteEnabled: value, + }); + }; + + const handleGroupNameChange = (e: React.ChangeEvent) => { + onChange({ + ...formData, + groupName: e.target.value, + }); + }; + + const handleFixedMembersSelect = (friends: FriendSelectionItem[]) => { + onChange({ + ...formData, + fixedGroupMembers: friends, + }); + }; + + return ( + + + + + + 拉群设置 + + + 开启后,可配置群名称和固定群成员,将用户引导到指定微信群 + + + + + + {formData.groupInviteEnabled && ( + + + {/* 固定成员选择,复用好友选择组件 */} + + + 固定群成员 + + + + + )} + + + ); +}; + +export default GroupSettings; diff --git a/Server/.gitignore b/Server/.gitignore index 39ab957f..1047f5bc 100644 --- a/Server/.gitignore +++ b/Server/.gitignore @@ -16,3 +16,4 @@ nginx.htaccess .cursor/ thinkphp/ public/static/ +*.log diff --git a/Server/application/api/controller/WebSocketController.php b/Server/application/api/controller/WebSocketController.php index 69afb2e6..5c5e3255 100644 --- a/Server/application/api/controller/WebSocketController.php +++ b/Server/application/api/controller/WebSocketController.php @@ -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]); diff --git a/Server/application/cunkebao/controller/plan/GetAddFriendPlanDetailV1Controller.php b/Server/application/cunkebao/controller/plan/GetAddFriendPlanDetailV1Controller.php index 3d262aba..7f5f609c 100644 --- a/Server/application/cunkebao/controller/plan/GetAddFriendPlanDetailV1Controller.php +++ b/Server/application/cunkebao/controller/plan/GetAddFriendPlanDetailV1Controller.php @@ -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, diff --git a/Server/application/cunkebao/controller/plan/PostCreateAddFriendPlanV1Controller.php b/Server/application/cunkebao/controller/plan/PostCreateAddFriendPlanV1Controller.php index e7bdb525..4a727d5b 100644 --- a/Server/application/cunkebao/controller/plan/PostCreateAddFriendPlanV1Controller.php +++ b/Server/application/cunkebao/controller/plan/PostCreateAddFriendPlanV1Controller.php @@ -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(), diff --git a/Server/application/cunkebao/controller/plan/PostUpdateAddFriendPlanV1Controller.php b/Server/application/cunkebao/controller/plan/PostUpdateAddFriendPlanV1Controller.php index 95a44689..d55662fe 100644 --- a/Server/application/cunkebao/controller/plan/PostUpdateAddFriendPlanV1Controller.php +++ b/Server/application/cunkebao/controller/plan/PostUpdateAddFriendPlanV1Controller.php @@ -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(), ]; diff --git a/Server/application/cunkebao/controller/workbench/WorkbenchController.php b/Server/application/cunkebao/controller/workbench/WorkbenchController.php index f15f6428..60bdbef4 100644 --- a/Server/application/cunkebao/controller/workbench/WorkbenchController.php +++ b/Server/application/cunkebao/controller/workbench/WorkbenchController.php @@ -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()]); + } } diff --git a/Server/application/cunkebao/controller/workbench/WorkbenchController_new.php b/Server/application/cunkebao/controller/workbench/WorkbenchController_new.php deleted file mode 100644 index 8363ea63..00000000 --- a/Server/application/cunkebao/controller/workbench/WorkbenchController_new.php +++ /dev/null @@ -1,3006 +0,0 @@ -request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - // 获取登录用户信息 - $userInfo = request()->userInfo; - - // 获取请求参数 - $param = $this->request->post(); - - - // 根据业务默认值补全参数 - if ( - isset($param['type']) && - intval($param['type']) === self::TYPE_GROUP_PUSH - ) { - if (empty($param['startTime'])) { - $param['startTime'] = '09:00'; - } - if (empty($param['endTime'])) { - $param['endTime'] = '21:00'; - } - } - - // 验证数据 - $validate = new WorkbenchValidate; - if (!$validate->scene('create')->check($param)) { - return json(['code' => 400, 'msg' => $validate->getError()]); - } - - Db::startTrans(); - try { - // 创建工作台基本信息 - $workbench = new Workbench; - $workbench->name = $param['name']; - $workbench->type = $param['type']; - $workbench->status = !empty($param['status']) ? 1 : 0; - $workbench->autoStart = !empty($param['autoStart']) ? 1 : 0; - $workbench->userId = $userInfo['id']; - $workbench->companyId = $userInfo['companyId']; - $workbench->createTime = time(); - $workbench->updateTime = time(); - $workbench->save(); - - // 根据类型创建对应的配置 - switch ($param['type']) { - case self::TYPE_AUTO_LIKE: // 自动点赞 - $config = new WorkbenchAutoLike; - $config->workbenchId = $workbench->id; - $config->interval = $param['interval']; - $config->maxLikes = $param['maxLikes']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->contentTypes = json_encode($param['contentTypes']); - $config->devices = json_encode($param['deviceGroups']); - $config->friends = json_encode($param['wechatFriends']); - // $config->targetGroups = json_encode($param['targetGroups']); - // $config->tagOperator = $param['tagOperator']; - $config->friendMaxLikes = $param['friendMaxLikes']; - $config->friendTags = $param['friendTags']; - $config->enableFriendTags = $param['enableFriendTags']; - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_MOMENTS_SYNC: // 朋友圈同步 - $config = new WorkbenchMomentsSync; - $config->workbenchId = $workbench->id; - $config->syncInterval = $param['syncInterval']; - $config->syncCount = $param['syncCount']; - $config->syncType = $param['syncType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->accountType = $param['accountType']; - $config->devices = json_encode($param['deviceGroups']); - $config->contentLibraries = json_encode($param['contentGroups'] ?? []); - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_GROUP_PUSH: // 群消息推送 - $ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? []); - $groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds); - $groupPushData['workbenchId'] = $workbench->id; - $groupPushData['createTime'] = time(); - $groupPushData['updateTime'] = time(); - $config = new WorkbenchGroupPush; - $config->save($groupPushData); - break; - case self::TYPE_GROUP_CREATE: // 自动建群 - $config = new WorkbenchGroupCreate; - $config->workbenchId = $workbench->id; - $config->planType = !empty($param['planType']) ? $param['planType'] : 0; - $config->executorId = !empty($param['executorId']) ? $param['executorId'] : 0; - - $config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->startTime = $param['startTime'] ?? ''; - $config->endTime = $param['endTime'] ?? ''; - $config->groupSizeMin = intval($param['groupSizeMin'] ?? 3); - $config->groupSizeMax = intval($param['groupSizeMax'] ?? 38); - $config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20); - $config->groupNameTemplate = $param['groupNameTemplate'] ?? ''; - $config->groupDescription = $param['groupDescription'] ?? ''; - $config->poolGroups = json_encode($param['poolGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->wechatGroups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE); - - // 处理群管理员:如果启用了群管理员且有指定管理员,则保存到admins字段 - $admins = []; - if (!empty($param['groupAdminEnabled']) && !empty($param['groupAdminWechatId'])) { - // 如果groupAdminWechatId是数组,取第一个;如果是单个值,直接使用 - $adminWechatId = is_array($param['groupAdminWechatId']) ? $param['groupAdminWechatId'][0] : $param['groupAdminWechatId']; - // 如果是好友ID,直接添加到admins;如果是wechatId,需要转换为好友ID - if (is_numeric($adminWechatId)) { - $admins[] = intval($adminWechatId); - } else { - // 如果是wechatId字符串,需要查询对应的好友ID - $friend = Db::table('s2_wechat_friend')->where('wechatId', $adminWechatId)->find(); - if ($friend) { - $admins[] = intval($friend['id']); - } - } - } - // 如果传入了admins参数,优先使用(兼容旧逻辑) - if (!empty($param['admins']) && is_array($param['admins'])) { - $admins = array_merge($admins, $param['admins']); - } - $config->admins = json_encode(array_unique($admins), JSON_UNESCAPED_UNICODE); - - $config->fixedWechatIds = json_encode($param['fixedWechatIds'] ?? [], JSON_UNESCAPED_UNICODE); - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_TRAFFIC_DISTRIBUTION: // 流量分发 - $config = new WorkbenchTrafficConfig; - $config->workbenchId = $workbench->id; - $config->distributeType = $param['distributeType']; - $config->maxPerDay = $param['maxPerDay']; - $config->timeType = $param['timeType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->devices = json_encode($param['deviceGroups'], JSON_UNESCAPED_UNICODE); - $config->pools = json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE); - $config->account = json_encode($param['accountGroups'], JSON_UNESCAPED_UNICODE); - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = new WorkbenchImportContact; - $config->workbenchId = $workbench->id; - $config->devices = json_encode($param['deviceGroups'], JSON_UNESCAPED_UNICODE); - $config->pools = json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE); - $config->num = $param['num']; - $config->clearContact = $param['clearContact']; - $config->remark = $param['remark']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->createTime = time(); - $config->save(); - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '创建成功', 'data' => ['id' => $workbench->id]]); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]); - } - } - - /** - * 获取工作台列表 - * @return \think\response\Json - */ - public function getList() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $type = $this->request->param('type', ''); - $keyword = $this->request->param('keyword', ''); - - $where = [ - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - - - // 添加类型筛选 - if ($type !== '') { - $where[] = ['type', '=', $type]; - } - - // 添加名称模糊搜索 - if ($keyword !== '') { - $where[] = ['name', 'like', '%' . $keyword . '%']; - } - - // 定义关联关系 - $with = [ - 'autoLike' => function ($query) { - $query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends'); - }, - 'momentsSync' => function ($query) { - $query->field('workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries'); - }, - 'trafficConfig' => function ($query) { - $query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account'); - }, - 'groupPush' => function ($query) { - $query->field('workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt'); - }, - 'groupCreate' => function ($query) { - $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins'); - }, - 'importContact' => function ($query) { - $query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime'); - }, - 'user' => function ($query) { - $query->field('id,username'); - } - ]; - - $list = Workbench::where($where) - ->with($with) - ->field('id,companyId,name,type,status,autoStart,userId,createTime,updateTime') - ->order('id', 'desc') - ->page($page, $limit) - ->select() - ->each(function ($item) { - // 处理配置信息 - switch ($item->type) { - case self::TYPE_AUTO_LIKE: - if (!empty($item->autoLike)) { - $item->config = $item->autoLike; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->contentTypes = json_decode($item->config->contentTypes, true); - $item->config->friends = json_decode($item->config->friends, true); - - // 添加今日点赞数 - $startTime = strtotime(date('Y-m-d') . ' 00:00:00'); - $endTime = strtotime(date('Y-m-d') . ' 23:59:59'); - $todayLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $item->id) - ->whereTime('createTime', 'between', [$startTime, $endTime]) - ->count(); - - // 添加总点赞数 - $totalLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $item->id) - ->count(); - - $item->config->todayLikeCount = $todayLikeCount; - $item->config->totalLikeCount = $totalLikeCount; - } - unset($item->autoLike, $item->auto_like); - break; - case self::TYPE_MOMENTS_SYNC: - if (!empty($item->momentsSync)) { - $item->config = $item->momentsSync; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->contentGroups = json_decode($item->config->contentLibraries, true); - //同步记录 - $sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->count(); - $item->syncCount = $sendNum; - $lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->order('id DESC')->value('createTime'); - $item->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--'; - - - // 获取内容库名称 - if (!empty($item->config->contentGroups)) { - $libraryNames = ContentLibrary::where('id', 'in', $item->config->contentGroups)->select(); - $item->config->contentGroupsOptions = $libraryNames; - } else { - $item->config->contentGroupsOptions = []; - } - } - unset($item->momentsSync, $item->moments_sync, $item->config->contentLibraries); - break; - case self::TYPE_GROUP_PUSH: - if (!empty($item->groupPush)) { - $item->config = $item->groupPush; - $item->config->pushType = $item->config->pushType; - $item->config->targetType = isset($item->config->targetType) ? intval($item->config->targetType) : 1; // 默认1=群推送 - $item->config->groupPushSubType = isset($item->config->groupPushSubType) ? intval($item->config->groupPushSubType) : 1; // 默认1=群群发 - $item->config->startTime = $item->config->startTime; - $item->config->endTime = $item->config->endTime; - $item->config->maxPerDay = $item->config->maxPerDay; - $item->config->pushOrder = $item->config->pushOrder; - $item->config->isLoop = $item->config->isLoop; - $item->config->status = $item->config->status; - $item->config->ownerWechatIds = json_decode($item->config->ownerWechatIds ?? '[]', true) ?: []; - // 根据targetType解析不同的数据 - if ($item->config->targetType == 1) { - // 群推送 - $item->config->wechatGroups = json_decode($item->config->groups, true) ?: []; - $item->config->wechatFriends = []; - // 群推送不需要devices字段 - // 群公告相关字段 - if ($item->config->groupPushSubType == 2) { - $item->config->announcementContent = isset($item->config->announcementContent) ? $item->config->announcementContent : ''; - $item->config->enableAiRewrite = isset($item->config->enableAiRewrite) ? intval($item->config->enableAiRewrite) : 0; - $item->config->aiRewritePrompt = isset($item->config->aiRewritePrompt) ? $item->config->aiRewritePrompt : ''; - } - $item->config->trafficPools = []; - } else { - // 好友推送 - $item->config->wechatFriends = json_decode($item->config->friends, true) ?: []; - $item->config->wechatGroups = []; - $item->config->trafficPools = json_decode($item->config->trafficPools ?? '[]', true) ?: []; - } - $item->config->contentLibraries = json_decode($item->config->contentLibraries, true); - $item->config->postPushTags = json_decode($item->config->postPushTags ?? '[]', true) ?: []; - $item->config->lastPushTime = ''; - if (!empty($item->config->ownerWechatIds)) { - $ownerWechatOptions = Db::name('wechat_account') - ->whereIn('id', $item->config->ownerWechatIds) - ->field('id,wechatId,nickName,avatar,alias') - ->select(); - $item->config->ownerWechatOptions = $ownerWechatOptions; - } else { - $item->config->ownerWechatOptions = []; - } - } - unset($item->groupPush, $item->group_push); - break; - case self::TYPE_GROUP_CREATE: - if (!empty($item->groupCreate)) { - $item->config = $item->groupCreate; - $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) ?: []; - - // 处理群管理员相关字段 - $item->config->groupAdminEnabled = !empty($item->config->admins) ? 1 : 0; - - 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; - // 如果有管理员,设置groupAdminWechatId为第一个管理员的ID(用于前端回显) - $item->config->groupAdminWechatId = !empty($item->config->admins) ? $item->config->admins[0] : null; - } else { - $item->config->adminsOptions = []; - $item->config->groupAdminWechatId = null; - } - } - unset($item->groupCreate, $item->group_create); - break; - case self::TYPE_TRAFFIC_DISTRIBUTION: - if (!empty($item->trafficConfig)) { - $item->config = $item->trafficConfig; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->poolGroups = json_decode($item->config->pools, true); - $item->config->account = json_decode($item->config->account, true); - $config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $item->id])->order('id DESC')->find(); - $item->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--'; - - //统计 - $labels = $item->config->poolGroups; - $totalUsers = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where([ - ['wf.isDeleted', '=', 0], - ['sa.departmentId', '=', $item->companyId] - ]) - ->whereIn('wa.currentDeviceId', $item->config->devices); - - if (!empty($labels) && count($labels) > 0) { - $totalUsers = $totalUsers->where(function ($q) use ($labels) { - foreach ($labels as $label) { - $q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')"); - } - }); - } - - $totalUsers = $totalUsers->count(); - $totalAccounts = count($item->config->account); - $dailyAverage = Db::name('workbench_traffic_config_item') - ->where('workbenchId', $item->id) - ->count(); - $day = (time() - strtotime($item->createTime)) / 86400; - $day = intval($day); - if ($dailyAverage > 0 && $totalAccounts > 0 && $day > 0) { - $dailyAverage = $dailyAverage / $totalAccounts / $day; - } - $item->config->total = [ - 'dailyAverage' => intval($dailyAverage), - 'totalAccounts' => $totalAccounts, - 'deviceCount' => count($item->config->devices), - 'poolCount' => !empty($item->config->poolGroups) ? count($item->config->poolGroups) : 'ALL', - 'totalUsers' => $totalUsers >> 0 - ]; - } - unset($item->trafficConfig, $item->traffic_config); - break; - - case self::TYPE_IMPORT_CONTACT: - if (!empty($item->importContact)) { - $item->config = $item->importContact; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->poolGroups = json_decode($item->config->pools, true); - } - unset($item->importContact, $item->import_contact); - break; - } - // 添加创建人名称 - $item['creatorName'] = $item->user ? $item->user->username : ''; - unset($item['user']); // 移除关联数据 - return $item; - }); - - $total = Workbench::where($where)->count(); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取工作台详情 - * @param int $id 工作台ID - * @return \think\response\Json - */ - public function detail() - { - $id = $this->request->param('id', ''); - - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 定义关联关系 - $with = [ - 'autoLike' => function ($query) { - $query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends,friendMaxLikes,friendTags,enableFriendTags'); - }, - 'momentsSync' => function ($query) { - $query->field('workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries'); - }, - 'trafficConfig' => function ($query) { - $query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account'); - }, - 'groupPush' => function ($query) { - $query->field('workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt'); - }, - 'groupCreate' => function ($query) { - $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins'); - }, - 'importContact' => function ($query) { - $query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime'); - }, - ]; - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - - $workbench = Workbench::where($where) - ->field('id,name,type,status,autoStart,createTime,updateTime,companyId') - ->with($with) - ->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - // 处理配置信息 - switch ($workbench->type) { - //自动点赞 - case self::TYPE_AUTO_LIKE: - if (!empty($workbench->autoLike)) { - $workbench->config = $workbench->autoLike; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->wechatFriends = json_decode($workbench->config->friends, true); - $workbench->config->targetType = 2; - //$workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true); - $workbench->config->contentTypes = json_decode($workbench->config->contentTypes, true); - - // 添加今日点赞数 - $startTime = strtotime(date('Y-m-d') . ' 00:00:00'); - $endTime = strtotime(date('Y-m-d') . ' 23:59:59'); - $todayLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $workbench->id) - ->whereTime('createTime', 'between', [$startTime, $endTime]) - ->count(); - - // 添加总点赞数 - $totalLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $workbench->id) - ->count(); - - $workbench->config->todayLikeCount = $todayLikeCount; - $workbench->config->totalLikeCount = $totalLikeCount; - - unset($workbench->autoLike, $workbench->auto_like); - } - break; - //自动同步朋友圈 - case self::TYPE_MOMENTS_SYNC: - if (!empty($workbench->momentsSync)) { - $workbench->config = $workbench->momentsSync; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->contentGroups = json_decode($workbench->config->contentLibraries, true); - - //同步记录 - $sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->count(); - $workbench->syncCount = $sendNum; - $lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->value('createTime'); - $workbench->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--'; - unset($workbench->momentsSync, $workbench->moments_sync); - } - break; - //群推送 - case self::TYPE_GROUP_PUSH: - if (!empty($workbench->groupPush)) { - $workbench->config = $workbench->groupPush; - $workbench->config->targetType = isset($workbench->config->targetType) ? intval($workbench->config->targetType) : 1; // 默认1=群推送 - $workbench->config->groupPushSubType = isset($workbench->config->groupPushSubType) ? intval($workbench->config->groupPushSubType) : 1; // 默认1=群群发 - $workbench->config->ownerWechatIds = json_decode($workbench->config->ownerWechatIds ?? '[]', true) ?: []; - // 根据targetType解析不同的数据 - if ($workbench->config->targetType == 1) { - // 群推送 - $workbench->config->wechatGroups = json_decode($workbench->config->groups, true) ?: []; - $workbench->config->wechatFriends = []; - $workbench->config->trafficPools = []; - // 群推送不需要devices字段 - // 群公告相关字段 - if ($workbench->config->groupPushSubType == 2) { - $workbench->config->announcementContent = isset($workbench->config->announcementContent) ? $workbench->config->announcementContent : ''; - $workbench->config->enableAiRewrite = isset($workbench->config->enableAiRewrite) ? intval($workbench->config->enableAiRewrite) : 0; - $workbench->config->aiRewritePrompt = isset($workbench->config->aiRewritePrompt) ? $workbench->config->aiRewritePrompt : ''; - } - } else { - // 好友推送 - $workbench->config->wechatFriends = json_decode($workbench->config->friends, true) ?: []; - $workbench->config->wechatGroups = []; - $workbench->config->trafficPools = json_decode($workbench->config->trafficPools ?? '[]', true) ?: []; - } - $workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true); - $workbench->config->postPushTags = json_decode($workbench->config->postPushTags ?? '[]', true) ?: []; - unset($workbench->groupPush, $workbench->group_push); - } - break; - //建群助手 - case self::TYPE_GROUP_CREATE: - if (!empty($workbench->groupCreate)) { - $workbench->config = $workbench->groupCreate; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->poolGroups = json_decode($workbench->config->poolGroups, true); - $workbench->config->wechatGroups = json_decode($workbench->config->wechatGroups, true); - $workbench->config->admins = json_decode($workbench->config->admins ?? '[]', true) ?: []; - - // 处理群管理员相关字段 - $workbench->config->groupAdminEnabled = !empty($workbench->config->admins) ? 1 : 0; - - // 如果有管理员,设置groupAdminWechatId为第一个管理员的ID(用于前端回显) - $workbench->config->groupAdminWechatId = !empty($workbench->config->admins) ? $workbench->config->admins[0] : null; - - // 统计已建群数(状态为成功且groupId不为空的记录,按groupId分组去重) - $createdGroupsCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbench->id) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->where('groupId', '<>', null) - ->group('groupId') - ->count(); - - // 统计总人数(该工作台的所有记录数) - $totalMembersCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbench->id) - ->count(); - - // 添加统计信息 - $workbench->config->stats = [ - 'createdGroupsCount' => $createdGroupsCount, - 'totalMembersCount' => $totalMembersCount - ]; - - unset($workbench->groupCreate, $workbench->group_create); - } - break; - //流量分发 - case self::TYPE_TRAFFIC_DISTRIBUTION: - if (!empty($workbench->trafficConfig)) { - $workbench->config = $workbench->trafficConfig; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->accountGroups = json_decode($workbench->config->account, true); - $workbench->config->poolGroups = json_decode($workbench->config->pools, true); - $config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->find(); - $workbench->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--'; - - //统计 - $labels = $workbench->config->poolGroups; - $totalUsers = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where([ - ['wf.isDeleted', '=', 0], - ['sa.departmentId', '=', $workbench->companyId] - ]) - ->whereIn('wa.currentDeviceId', $workbench->config->deviceGroups) - ->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId') - ->where(function ($q) use ($labels) { - foreach ($labels as $label) { - $q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')"); - } - })->count(); - - $totalAccounts = Db::table('s2_company_account') - ->alias('a') - ->where(['a.departmentId' => $workbench->companyId, 'a.status' => 0]) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%') - ->group('a.id') - ->count(); - - $dailyAverage = Db::name('workbench_traffic_config_item') - ->where('workbenchId', $workbench->id) - ->count(); - $day = (time() - strtotime($workbench->createTime)) / 86400; - $day = intval($day); - - - if ($dailyAverage > 0) { - $dailyAverage = $dailyAverage / $totalAccounts / $day; - } - - $workbench->config->total = [ - 'dailyAverage' => intval($dailyAverage), - 'totalAccounts' => $totalAccounts, - 'deviceCount' => count($workbench->config->deviceGroups), - 'poolCount' => count($workbench->config->poolGroups), - 'totalUsers' => $totalUsers >> 0 - ]; - unset($workbench->trafficConfig, $workbench->traffic_config); - } - break; - case self::TYPE_IMPORT_CONTACT: - if (!empty($workbench->importContact)) { - $workbench->config = $workbench->importContact; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->poolGroups = json_decode($workbench->config->pools, true); - } - unset($workbench->importContact, $workbench->import_contact); - break; - } - unset( - $workbench->autoLike, - $workbench->momentsSync, - $workbench->groupPush, - $workbench->groupCreate, - $workbench->config->devices, - $workbench->config->friends, - $workbench->config->groups, - $workbench->config->contentLibraries, - $workbench->config->account, - ); - - - //获取设备信息 - if (!empty($workbench->config->deviceGroups)) { - $deviceList = DeviceModel::alias('d') - ->field([ - 'd.id', 'd.imei', 'd.memo', 'd.alive', - 'l.wechatId', - 'a.nickname', 'a.alias', 'a.avatar', 'a.alias', '0 totalFriend' - ]) - ->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId') - ->leftJoin('wechat_account a', 'l.wechatId = a.wechatId') - ->whereIn('d.id', $workbench->config->deviceGroups) - ->order('d.id desc') - ->select(); - - foreach ($deviceList as &$device) { - $curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find(); - $device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0; - } - unset($device); - - $workbench->config->deviceGroupsOptions = $deviceList; - } else { - $workbench->config->deviceGroupsOptions = []; - } - - - // 获取群(当targetType=1时) - if (!empty($workbench->config->wechatGroups) && isset($workbench->config->targetType) && $workbench->config->targetType == 1) { - $groupList = Db::name('wechat_group')->alias('wg') - ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') - ->where('wg.id', 'in', $workbench->config->wechatGroups) - ->order('wg.id', 'desc') - ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wa.avatar,wa.alias,wg.avatar as groupAvatar') - ->select(); - $workbench->config->wechatGroupsOptions = $groupList; - } else { - $workbench->config->wechatGroupsOptions = []; - } - - // 获取好友(当targetType=2时) - if (!empty($workbench->config->wechatFriends) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { - $friendList = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where('wf.id', 'in', $workbench->config->wechatFriends) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar') - ->select(); - $workbench->config->wechatFriendsOptions = $friendList; - } else { - $workbench->config->wechatFriendsOptions = []; - } - - // 获取流量池(当targetType=2时) - if (!empty($workbench->config->trafficPools) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { - $poolList = Db::name('traffic_source_package')->alias('tsp') - ->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0') - ->whereIn('tsp.id', $workbench->config->trafficPools) - ->where('tsp.isDel', 0) - ->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0]) - ->field('tsp.id,tsp.name,tsp.description,tsp.pic,COUNT(tspi.id) as itemCount') - ->group('tsp.id') - ->order('tsp.id', 'desc') - ->select(); - $workbench->config->trafficPoolsOptions = $poolList; - } else { - $workbench->config->trafficPoolsOptions = []; - } - - // 获取内容库名称 - if (!empty($workbench->config->contentGroups)) { - $libraryNames = ContentLibrary::where('id', 'in', $workbench->config->contentGroups)->select(); - $workbench->config->contentGroupsOptions = $libraryNames; - } else { - $workbench->config->contentGroupsOptions = []; - } - - //账号 - if (!empty($workbench->config->accountGroups)) { - $account = Db::table('s2_company_account')->alias('a') - ->where(['a.departmentId' => $this->request->userInfo['companyId'], 'a.status' => 0]) - ->whereIn('a.id', $workbench->config->accountGroups) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%') - ->field('a.id,a.userName,a.realName,a.nickname,a.memo') - ->select(); - $workbench->config->accountGroupsOptions = $account; - } else { - $workbench->config->accountGroupsOptions = []; - } - - if (!empty($workbench->config->poolGroups)) { - $poolGroupsOptions = Db::name('traffic_source_package')->alias('tsp') - ->join('traffic_source_package_item tspi', 'tspi.packageId=tsp.id', 'left') - ->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0]) - ->whereIn('tsp.id', $workbench->config->poolGroups) - ->field('tsp.id,tsp.name,tsp.description,tsp.createTime,count(tspi.id) as num') - ->group('tsp.id') - ->select(); - $workbench->config->poolGroupsOptions = $poolGroupsOptions; - } else { - $workbench->config->poolGroupsOptions = []; - } - - if (!empty($workbench->config->ownerWechatIds)) { - $ownerWechatOptions = Db::name('wechat_account') - ->whereIn('id', $workbench->config->ownerWechatIds) - ->field('id,wechatId,nickName,avatar,alias') - ->select(); - $workbench->config->ownerWechatOptions = $ownerWechatOptions; - } else { - $workbench->config->ownerWechatOptions = []; - } - - // 获取群组选项(自动建群) - if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->wechatGroups)) { - // 分离数字ID(好友ID)和字符串ID(手动创建的群组) - $friendIds = []; - $manualGroupIds = []; - - foreach ($workbench->config->wechatGroups as $groupId) { - if (is_numeric($groupId)) { - $friendIds[] = intval($groupId); - } else { - $manualGroupIds[] = $groupId; - } - } - - $wechatGroupsOptions = []; - - // 查询好友信息(数字ID) - if (!empty($friendIds)) { - $friendList = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->join(['s2_company_account' => 'ca'], 'ca.id = wf.accountId', 'left') - ->where('wf.id', 'in', $friendIds) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname,wf.avatar,wf.alias,wf.gender,wf.phone,wa.nickName as accountNickname,ca.userName as account,ca.realName as username,wf.createTime,wf.updateTime,wf.deleteTime,wf.ownerWechatId') - ->select(); - - // 获取群主信息 - foreach ($friendList as &$friend) { - if (!empty($friend['ownerWechatId'])) { - $owner = Db::name('wechat_account') - ->where('wechatId', $friend['ownerWechatId']) - ->field('nickName,alias') - ->find(); - $friend['ownerNickname'] = $owner['nickName'] ?? ''; - $friend['ownerAlias'] = $owner['alias'] ?? ''; - } else { - $friend['ownerNickname'] = ''; - $friend['ownerAlias'] = ''; - } - $friend['isManual'] = ''; - // 格式化时间 - $friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : ''; - $friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : ''; - $friend['deleteTime'] = !empty($friend['deleteTime']) ? date('Y-m-d H:i:s', $friend['deleteTime']) : ''; - } - unset($friend); - - $wechatGroupsOptions = array_merge($wechatGroupsOptions, $friendList); - } - - // 处理手动创建的群组(字符串ID) - if (!empty($manualGroupIds)) { - foreach ($manualGroupIds as $groupId) { - // 手动创建的群组,只返回基本信息 - $wechatGroupsOptions[] = [ - 'id' => $groupId, - 'wechatId' => $groupId, - 'nickname' => $groupId, - 'avatar' => '', - 'isManual' => 1 - ]; - } - } - - $workbench->config->wechatGroupsOptions = $wechatGroupsOptions; - } else { - $workbench->config->wechatGroupsOptions = []; - } - - // 获取管理员选项(自动建群) - if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->admins)) { - $adminOptions = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where('wf.id', 'in', $workbench->config->admins) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar') - ->select(); - $workbench->config->adminsOptions = $adminOptions; - } else { - $workbench->config->adminsOptions = []; - } - - return json(['code' => 200, 'msg' => '获取成功', 'data' => $workbench]); - } - - /** - * 更新工作台 - * @return \think\response\Json - */ - public function update() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - // 获取请求参数 - $param = $this->request->post(); - - // 验证数据 - $validate = new WorkbenchValidate; - if (!$validate->scene('update')->check($param)) { - return json(['code' => 400, 'msg' => $validate->getError()]); - } - - - $where = [ - ['id', '=', $param['id']], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - // 查询工作台是否存在 - $workbench = Workbench::where($where)->find(); - if (!$workbench) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - Db::startTrans(); - try { - // 更新工作台基本信息 - $workbench->name = $param['name']; - $workbench->status = !empty($param['status']) ? 1 : 0; - $workbench->autoStart = !empty($param['autoStart']) ? 1 : 0; - $workbench->updateTime = time(); - $workbench->save(); - - // 根据类型更新对应的配置 - switch ($workbench->type) { - case self::TYPE_AUTO_LIKE: - $config = WorkbenchAutoLike::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->interval = $param['interval']; - $config->maxLikes = $param['maxLikes']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->contentTypes = json_encode($param['contentTypes']); - $config->devices = json_encode($param['deviceGroups']); - $config->friends = json_encode($param['wechatFriends']); - // $config->targetGroups = json_encode($param['targetGroups']); - // $config->tagOperator = $param['tagOperator']; - $config->friendMaxLikes = $param['friendMaxLikes']; - $config->friendTags = $param['friendTags']; - $config->enableFriendTags = $param['enableFriendTags']; - $config->updateTime = time(); - $config->save(); - } - break; - - case self::TYPE_MOMENTS_SYNC: - $config = WorkbenchMomentsSync::where('workbenchId', $param['id'])->find(); - if ($config) { - if (!empty($param['contentGroups'])) { - foreach ($param['contentGroups'] as $library) { - if (isset($library['id']) && !empty($library['id'])) { - $contentLibraries[] = $library['id']; - } else { - $contentLibraries[] = $library; - } - } - } else { - $contentLibraries = []; - } - - $config->syncInterval = $param['syncInterval']; - $config->syncCount = $param['syncCount']; - $config->syncType = $param['syncType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->accountType = $param['accountType']; - $config->devices = json_encode($param['deviceGroups']); - $config->contentLibraries = json_encode($contentLibraries); - $config->updateTime = time(); - $config->save(); - } - break; - - case self::TYPE_GROUP_PUSH: - $config = WorkbenchGroupPush::where('workbenchId', $param['id'])->find(); - if ($config) { - $ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? null, $config); - $groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds, $config); - $groupPushData['updateTime'] = time(); - $config->save($groupPushData); - } - break; - - case self::TYPE_GROUP_CREATE: - $config = WorkbenchGroupCreate::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->startTime = $param['startTime'] ?? ''; - $config->endTime = $param['endTime'] ?? ''; - $config->groupSizeMin = intval($param['groupSizeMin'] ?? 3); - $config->groupSizeMax = intval($param['groupSizeMax'] ?? 38); - $config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20); - $config->groupNameTemplate = $param['groupNameTemplate'] ?? ''; - $config->groupDescription = $param['groupDescription'] ?? ''; - $config->poolGroups = json_encode($param['poolGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->wechatGroups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE); - - // 处理群管理员:如果启用了群管理员且有指定管理员,则保存到admins字段 - $admins = []; - if (!empty($param['groupAdminEnabled']) && !empty($param['groupAdminWechatId'])) { - // 如果groupAdminWechatId是数组,取第一个;如果是单个值,直接使用 - $adminWechatId = is_array($param['groupAdminWechatId']) ? $param['groupAdminWechatId'][0] : $param['groupAdminWechatId']; - // 如果是好友ID,直接添加到admins;如果是wechatId,需要转换为好友ID - if (is_numeric($adminWechatId)) { - $admins[] = intval($adminWechatId); - } else { - // 如果是wechatId字符串,需要查询对应的好友ID - $friend = Db::table('s2_wechat_friend')->where('wechatId', $adminWechatId)->find(); - if ($friend) { - $admins[] = intval($friend['id']); - } - } - } - // 如果传入了admins参数,优先使用(兼容旧逻辑) - if (!empty($param['admins']) && is_array($param['admins'])) { - $admins = array_merge($admins, $param['admins']); - } - $config->admins = json_encode(array_unique($admins), JSON_UNESCAPED_UNICODE); - - $config->updateTime = time(); - $config->save(); - } - break; - case self::TYPE_TRAFFIC_DISTRIBUTION: - $config = WorkbenchTrafficConfig::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->distributeType = $param['distributeType']; - $config->maxPerDay = $param['maxPerDay']; - $config->timeType = $param['timeType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->devices = json_encode($param['deviceGroups']); - $config->pools = json_encode($param['poolGroups']); - $config->account = json_encode($param['accountGroups']); - $config->updateTime = time(); - $config->save(); - } - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = WorkbenchImportContact::where('workbenchId', $param['id'])->find();; - if ($config) { - $config->devices = json_encode($param['deviceGroups']); - $config->pools = json_encode($param['poolGroups']); - $config->num = $param['num']; - $config->clearContact = $param['clearContact']; - $config->remark = $param['remark']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->save(); - } - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '更新成功']); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]); - } - } - - /** - * 更新工作台状态 - * @return \think\response\Json - */ - public function updateStatus() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - $id = $this->request->param('id', ''); - - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - $workbench->status = !$workbench['status']; - $workbench->save(); - - return json(['code' => 200, 'msg' => '更新成功']); - } - - /** - * 删除工作台(软删除) - */ - public function delete() - { - $id = $this->request->param('id'); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - $workbench = Workbench::where($where)->find(); - - if (!$workbench) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - // 软删除 - $workbench->isDel = 1; - $workbench->deleteTime = time(); - $workbench->save(); - - return json(['code' => 200, 'msg' => '删除成功']); - } - - /** - * 拷贝工作台 - * @return \think\response\Json - */ - public function copy() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - $id = $this->request->post('id'); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 验证权限并获取原数据 - $workbench = Workbench::where([ - ['id', '=', $id], - ['userId', '=', $this->request->userInfo['id']] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - Db::startTrans(); - try { - // 创建新的工作台基本信息 - $newWorkbench = new Workbench; - $newWorkbench->name = $workbench->name . ' copy'; - $newWorkbench->type = $workbench->type; - $newWorkbench->status = 1; // 新拷贝的默认启用 - $newWorkbench->autoStart = $workbench->autoStart; - $newWorkbench->userId = $this->request->userInfo['id']; - $newWorkbench->companyId = $this->request->userInfo['companyId']; - $newWorkbench->save(); - - // 根据类型拷贝对应的配置 - switch ($workbench->type) { - case self::TYPE_AUTO_LIKE: - $config = WorkbenchAutoLike::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchAutoLike; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->interval = $config->interval; - $newConfig->maxLikes = $config->maxLikes; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->contentTypes = $config->contentTypes; - $newConfig->devices = $config->devices; - $newConfig->friends = $config->friends; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_MOMENTS_SYNC: - $config = WorkbenchMomentsSync::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchMomentsSync; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->syncInterval = $config->syncInterval; - $newConfig->syncCount = $config->syncCount; - $newConfig->syncType = $config->syncType; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->accountType = $config->accountType; - $newConfig->devices = $config->devices; - $newConfig->contentLibraries = $config->contentLibraries; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_GROUP_PUSH: - $config = WorkbenchGroupPush::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchGroupPush; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->pushType = $config->pushType; - $newConfig->targetType = isset($config->targetType) ? $config->targetType : 1; // 默认1=群推送 - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->maxPerDay = $config->maxPerDay; - $newConfig->pushOrder = $config->pushOrder; - $newConfig->isLoop = $config->isLoop; - $newConfig->status = $config->status; - $newConfig->groups = $config->groups; - $newConfig->friends = $config->friends; - $newConfig->contentLibraries = $config->contentLibraries; - $newConfig->trafficPools = property_exists($config, 'trafficPools') ? $config->trafficPools : json_encode([], JSON_UNESCAPED_UNICODE); - $newConfig->socialMediaId = $config->socialMediaId; - $newConfig->promotionSiteId = $config->promotionSiteId; - $newConfig->ownerWechatIds = $config->ownerWechatIds; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_GROUP_CREATE: - $config = WorkbenchGroupCreate::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchGroupCreate; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->devices = $config->devices; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->groupSizeMin = $config->groupSizeMin; - $newConfig->groupSizeMax = $config->groupSizeMax; - $newConfig->maxGroupsPerDay = $config->maxGroupsPerDay; - $newConfig->groupNameTemplate = $config->groupNameTemplate; - $newConfig->groupDescription = $config->groupDescription; - $newConfig->poolGroups = $config->poolGroups; - $newConfig->wechatGroups = $config->wechatGroups; - $newConfig->admins = $config->admins ?? json_encode([], JSON_UNESCAPED_UNICODE); - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = WorkbenchImportContact::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchImportContact; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->devices = $config->devices; - $newConfig->pools = $config->pools; - $newConfig->num = $config->num; - $newConfig->clearContact = $config->clearContact; - $newConfig->remark = $config->remark; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->createTime = time(); - $newConfig->save(); - } - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '拷贝成功', 'data' => ['id' => $newWorkbench->id]]); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '拷贝失败:' . $e->getMessage()]); - } - } - - /** - * 获取点赞记录列表 - * @return \think\response\Json - */ - public function getLikeRecords() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchAutoLikeController(); - $controller->request = $this->request; - return $controller->getLikeRecords(); - } - - /** - * 获取朋友圈发布记录列表 - * @return \think\response\Json - */ - public function getMomentsRecords() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController(); - $controller->request = $this->request; - return $controller->getMomentsRecords(); - } - - /** - * 获取朋友圈发布统计 - * @return \think\response\Json - */ - public function getMomentsStats() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController(); - $controller->request = $this->request; - return $controller->getMomentsStats(); - } - - /** - * 获取流量分发记录列表 - * @return \think\response\Json - */ - public function getTrafficDistributionRecords() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchTrafficController(); - $controller->request = $this->request; - return $controller->getTrafficDistributionRecords(); - } - - /** - * 获取流量分发统计 - * @return \think\response\Json - */ - public function getTrafficDistributionStats() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchTrafficController(); - $controller->request = $this->request; - return $controller->getTrafficDistributionStats(); - } - - /** - * 获取流量分发详情 - * @return \think\response\Json - */ - public function getTrafficDistributionDetail() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchTrafficController(); - $controller->request = $this->request; - return $controller->getTrafficDistributionDetail(); - } - - /** - * 创建流量分发计划 - * @return \think\response\Json - */ - public function createTrafficPlan() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchTrafficController(); - $controller->request = $this->request; - return $controller->createTrafficPlan(); - } - - /** - * 获取流量列表 - * @return \think\response\Json - */ - public function getTrafficList() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchTrafficController(); - $controller->request = $this->request; - return $controller->getTrafficList(); - } - - /** - * 获取所有微信好友标签及数量统计 - * @return \think\response\Json - */ - public function getDeviceLabels() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchHelperController(); - $controller->request = $this->request; - return $controller->getDeviceLabels(); - } - - /** - * 获取群列表 - * @return \think\response\Json - */ - public function getGroupList() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchHelperController(); - $controller->request = $this->request; - return $controller->getGroupList(); - } - - /** - * 获取流量池列表 - * @return \think\response\Json - */ - public function getTrafficPoolList() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchHelperController(); - $controller->request = $this->request; - return $controller->getTrafficPoolList(); - } - - /** - * 获取账号列表 - * @return \think\response\Json - */ - public function getAccountList() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchHelperController(); - $controller->request = $this->request; - return $controller->getAccountList(); - } - - /** - * 获取京东联盟导购媒体 - * @return \think\response\Json - */ - public function getJdSocialMedia() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchHelperController(); - $controller->request = $this->request; - return $controller->getJdSocialMedia(); - } - - /** - * 获取京东联盟广告位 - * @return \think\response\Json - */ - public function getJdPromotionSite() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchHelperController(); - $controller->request = $this->request; - return $controller->getJdPromotionSite(); - } - - /** - * 京东转链-京推推 - * @param string $content - * @param string $positionid - * @return string - */ - public function changeLink($content = '', $positionid = '') - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchHelperController(); - $controller->request = $this->request; - return $controller->changeLink($content, $positionid); - } - - /** - * 规范化客服微信ID列表 - * @param mixed $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @return array - * @throws \Exception - */ - private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array - { - if ($ownerWechatIds === null) { - $existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : []; - if (empty($existing)) { - throw new \Exception('请至少选择一个客服微信'); - } - return $existing; - } - - if (!is_array($ownerWechatIds)) { - throw new \Exception('客服参数格式错误'); - } - - $normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误'); - if (empty($normalized)) { - throw new \Exception('请至少选择一个客服微信'); - } - return $normalized; - } - - /** - * 构建群推送配置数据 - * @param array $param - * @param array $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @return array - * @throws \Exception - */ - private function prepareGroupPushData(array $param, array $ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array - { - $targetTypeDefault = $originalConfig ? intval($originalConfig->targetType) : 1; - $targetType = intval($this->getParamValue($param, 'targetType', $targetTypeDefault)) ?: 1; - - $groupPushSubTypeDefault = $originalConfig ? intval($originalConfig->groupPushSubType) : 1; - $groupPushSubType = intval($this->getParamValue($param, 'groupPushSubType', $groupPushSubTypeDefault)) ?: 1; - if (!in_array($groupPushSubType, [1, 2], true)) { - $groupPushSubType = 1; - } - - $data = [ - 'pushType' => $this->toBoolInt($this->getParamValue($param, 'pushType', $originalConfig->pushType ?? 0)), - 'targetType' => $targetType, - 'startTime' => $this->getParamValue($param, 'startTime', $originalConfig->startTime ?? ''), - 'endTime' => $this->getParamValue($param, 'endTime', $originalConfig->endTime ?? ''), - 'maxPerDay' => intval($this->getParamValue($param, 'maxPerDay', $originalConfig->maxPerDay ?? 0)), - 'pushOrder' => $this->getParamValue($param, 'pushOrder', $originalConfig->pushOrder ?? 1), - 'groupPushSubType' => $groupPushSubType, - 'status' => $this->toBoolInt($this->getParamValue($param, 'status', $originalConfig->status ?? 0)), - 'socialMediaId' => $this->getParamValue($param, 'socialMediaId', $originalConfig->socialMediaId ?? ''), - 'promotionSiteId' => $this->getParamValue($param, 'promotionSiteId', $originalConfig->promotionSiteId ?? ''), - 'friendIntervalMin' => intval($this->getParamValue($param, 'friendIntervalMin', $originalConfig->friendIntervalMin ?? 10)), - 'friendIntervalMax' => intval($this->getParamValue($param, 'friendIntervalMax', $originalConfig->friendIntervalMax ?? 20)), - 'messageIntervalMin' => intval($this->getParamValue($param, 'messageIntervalMin', $originalConfig->messageIntervalMin ?? 1)), - 'messageIntervalMax' => intval($this->getParamValue($param, 'messageIntervalMax', $originalConfig->messageIntervalMax ?? 12)), - 'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)), - 'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE), - ]; - - if ($data['friendIntervalMin'] > $data['friendIntervalMax']) { - throw new \Exception('目标间最小间隔不能大于最大间隔'); - } - if ($data['messageIntervalMin'] > $data['messageIntervalMax']) { - throw new \Exception('消息间最小间隔不能大于最大间隔'); - } - - $contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : []; - $contentGroupsParam = $this->getParamValue($param, 'contentGroups', null); - $contentGroups = $contentGroupsParam !== null - ? $this->extractIdList($contentGroupsParam, '内容库参数格式错误') - : $contentGroupsExisting; - $data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE); - - $postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : []; - $postPushTagsParam = $this->getParamValue($param, 'postPushTags', null); - $postPushTags = $postPushTagsParam !== null - ? $this->extractIdList($postPushTagsParam, '推送标签参数格式错误') - : $postPushTagsExisting; - $data['postPushTags'] = json_encode($postPushTags, JSON_UNESCAPED_UNICODE); - - if ($targetType === 1) { - $data['isLoop'] = $this->toBoolInt($this->getParamValue($param, 'isLoop', $originalConfig->isLoop ?? 0)); - - $groupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->groups ?? []) : []; - $wechatGroups = array_key_exists('wechatGroups', $param) - ? $this->extractIdList($param['wechatGroups'], '群参数格式错误') - : $groupsExisting; - if (empty($wechatGroups)) { - throw new \Exception('群推送必须选择微信群'); - } - $data['groups'] = json_encode($wechatGroups, JSON_UNESCAPED_UNICODE); - $data['friends'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode([], JSON_UNESCAPED_UNICODE); - - if ($groupPushSubType === 2) { - $announcementContent = $this->getParamValue($param, 'announcementContent', $originalConfig->announcementContent ?? ''); - if (empty($announcementContent)) { - throw new \Exception('群公告必须输入公告内容'); - } - $enableAiRewrite = $this->toBoolInt($this->getParamValue($param, 'enableAiRewrite', $originalConfig->enableAiRewrite ?? 0)); - $aiRewritePrompt = trim((string)$this->getParamValue($param, 'aiRewritePrompt', $originalConfig->aiRewritePrompt ?? '')); - if ($enableAiRewrite === 1 && $aiRewritePrompt === '') { - throw new \Exception('启用AI智能话术改写时,必须输入改写提示词'); - } - $data['announcementContent'] = $announcementContent; - $data['enableAiRewrite'] = $enableAiRewrite; - $data['aiRewritePrompt'] = $aiRewritePrompt; - } else { - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - } else { - $data['isLoop'] = 0; - $friendsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->friends ?? []) : []; - $trafficPoolsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->trafficPools ?? []) : []; - - $friendTargets = array_key_exists('wechatFriends', $param) - ? $this->extractIdList($param['wechatFriends'], '好友参数格式错误') - : $friendsExisting; - $trafficPools = array_key_exists('trafficPools', $param) - ? $this->extractIdList($param['trafficPools'], '流量池参数格式错误') - : $trafficPoolsExisting; - - if (empty($friendTargets) && empty($trafficPools)) { - throw new \Exception('好友推送需至少选择好友或流量池'); - } - - $data['friends'] = json_encode($friendTargets, JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode($trafficPools, JSON_UNESCAPED_UNICODE); - $data['groups'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - - return $data; - } - - /** - * 获取参数值,若不存在则返回默认值 - * @param array $param - * @param string $key - * @param mixed $default - * @return mixed - */ - private function getParamValue(array $param, string $key, $default) - { - return array_key_exists($key, $param) ? $param[$key] : $default; - } - - /** - * 将值转换为整型布尔 - * @param mixed $value - * @return int - */ - private function toBoolInt($value): int - { - return empty($value) ? 0 : 1; - } - - /** - * 从参数中提取ID列表 - * @param mixed $items - * @param string $errorMessage - * @return array - * @throws \Exception - */ - private function extractIdList($items, string $errorMessage = '参数格式错误'): array - { - if (!is_array($items)) { - throw new \Exception($errorMessage); - } - - $ids = []; - foreach ($items as $item) { - if (is_array($item) && isset($item['id'])) { - $item = $item['id']; - } - if ($item === '' || $item === null) { - continue; - } - $ids[] = $item; - } - - return array_values(array_unique($ids)); - } - - /** - * 解码JSON数组 - * @param mixed $value - * @return array - */ - private function decodeJsonArray($value): array - { - if (empty($value)) { - return []; - } - if (is_array($value)) { - return $value; - } - $decoded = json_decode($value, true); - return is_array($decoded) ? $decoded : []; - } - - /** - * 验证内容是否包含链接 - * @param string $content 要检测的内容 - * @return bool - */ - private function containsLink($content) - { - // 定义各种链接的正则表达式模式 - $patterns = [ - // HTTP/HTTPS链接 - '/https?:\/\/[^\s]+/i', - // 京东商品链接 - '/item\.jd\.com\/\d+/i', - // 京东短链接 - '/u\.jd\.com\/[a-zA-Z0-9]+/i', - // 淘宝商品链接 - '/item\.taobao\.com\/item\.htm\?id=\d+/i', - // 天猫商品链接 - '/detail\.tmall\.com\/item\.htm\?id=\d+/i', - // 淘宝短链接 - '/m\.tb\.cn\/[a-zA-Z0-9]+/i', - // 拼多多链接 - '/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i', - // 苏宁易购链接 - '/product\.suning\.com\/\d+\/\d+\.html/i', - // 通用域名模式(包含常见电商域名) - '/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i', - // 通用短链接模式 - '/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i' - ]; - - // 遍历所有模式进行匹配 - foreach ($patterns as $pattern) { - if (preg_match($pattern, $content)) { - return true; - } - } - - return false; - } - - - /** - * 获取通讯录导入记录列表 - * @return \think\response\Json - */ - public function 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, - ] - ]); - } - - /** - * 获取群发统计数据 - * @return \think\response\Json - */ - public function getGroupPushStats() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchGroupPushController(); - $controller->request = $this->request; - return $controller->getGroupPushStats(); - } - - /** - * 计算基础统计数据 - */ - private function calculateBasicStats($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - // 获取工作台配置,计算计划发送数 - // 如果 workbenchId 为空,则查询所有工作台的配置 - $configQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - - if (!empty($workbenchId)) { - $configQuery->where('wgp.workbenchId', $workbenchId); - } else { - // 如果没有指定工作台ID,需要从 where 条件中获取 workbenchId 列表 - $workbenchIdCondition = null; - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - if ($condition[1] === 'in' && is_array($condition[2])) { - $workbenchIdCondition = $condition[2]; - break; - } elseif ($condition[1] === '=') { - $workbenchIdCondition = [$condition[2]]; - break; - } - } - } - if ($workbenchIdCondition) { - $configQuery->whereIn('wgp.workbenchId', $workbenchIdCondition); - } - } - - $configs = $configQuery->select(); - $targetType = 1; // 默认值 - if (!empty($configs)) { - // 如果只有一个配置,使用它的 targetType;如果有多个,默认使用1 - $targetType = intval($configs[0]->targetType ?? 1); - } - - // 计划发送数(根据配置计算) - $plannedSend = 0; - if (!empty($configs)) { - $days = ceil(($endTime - $startTime) / 86400); - foreach ($configs as $config) { - $maxPerDay = intval($config->maxPerDay ?? 0); - $configTargetType = intval($config->targetType ?? 1); - if ($configTargetType == 1) { - // 群推送:计划发送数 = 每日推送次数 * 天数 * 群数量 - $groups = $this->decodeJsonArray($config->groups ?? []); - $plannedSend += $maxPerDay * $days * count($groups); - } else { - // 好友推送:计划发送数 = 每日推送人数 * 天数 - $plannedSend += $maxPerDay * $days; - } - } - } - - // 构建查询条件 - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - // 实际成功发送数(从推送记录表统计) - $successSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->count(); - - // 触达率 = 成功发送数 / 计划发送数 - $reachRate = $plannedSend > 0 ? round(($successSend / $plannedSend) * 100, 1) : 0; - - // 获取发送记录列表,用于查询回复 - $sentItemIds = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - - // 回复统计(通过消息表查询) - $replyStats = $this->calculateReplyStats($sentItemIds, $targetType, $startTime, $endTime); - - // 链接点击统计 - $clickStats = $this->calculateClickStats($sentItemIds, $targetType, $startTime, $endTime); - - // 计算本月对比数据(简化处理,实际应该查询上个月同期数据) - $currentMonthStart = strtotime(date('Y-m-01 00:00:00')); - $lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month'))); - $lastMonthEnd = $currentMonthStart - 1; - - // 获取本月统计数据(避免递归调用) - $currentMonthWhere = [ - ['wgpi.createTime', '>=', $currentMonthStart] - ]; - // 复制 workbenchId 条件 - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - $currentMonthWhere[] = $condition; - break; - } - } - if ($contentIds !== null) { - $currentMonthWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - $currentMonthSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($currentMonthWhere) - ->count(); - - // 获取本月配置 - $currentMonthConfigQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - if (!empty($workbenchId)) { - $currentMonthConfigQuery->where('wgp.workbenchId', $workbenchId); - } else { - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') { - $currentMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]); - break; - } - } - } - $currentMonthConfigs = $currentMonthConfigQuery->select(); - $currentMonthPlanned = 0; - if (!empty($currentMonthConfigs)) { - $currentMonthDays = ceil(($endTime - $currentMonthStart) / 86400); - foreach ($currentMonthConfigs as $currentMonthConfig) { - $currentMonthMaxPerDay = intval($currentMonthConfig->maxPerDay ?? 0); - $currentMonthTargetType = intval($currentMonthConfig->targetType ?? 1); - if ($currentMonthTargetType == 1) { - $currentMonthGroups = $this->decodeJsonArray($currentMonthConfig->groups ?? []); - $currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays * count($currentMonthGroups); - } else { - $currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays; - } - } - } - $currentMonthReachRate = $currentMonthPlanned > 0 ? round(($currentMonthSend / $currentMonthPlanned) * 100, 1) : 0; - - // 获取上个月统计数据 - $lastMonthWhere = [ - ['wgpi.createTime', '>=', $lastMonthStart], - ['wgpi.createTime', '<=', $lastMonthEnd] - ]; - // 复制 workbenchId 条件 - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - $lastMonthWhere[] = $condition; - break; - } - } - if ($contentIds !== null) { - $lastMonthWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - $lastMonthSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($lastMonthWhere) - ->count(); - - // 获取上个月配置 - $lastMonthConfigQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - if (!empty($workbenchId)) { - $lastMonthConfigQuery->where('wgp.workbenchId', $workbenchId); - } else { - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') { - $lastMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]); - break; - } - } - } - $lastMonthConfigs = $lastMonthConfigQuery->select(); - - $lastMonthPlanned = 0; - if (!empty($lastMonthConfigs)) { - $lastMonthDays = ceil(($lastMonthEnd - $lastMonthStart) / 86400); - foreach ($lastMonthConfigs as $lastMonthConfig) { - $lastMonthMaxPerDay = intval($lastMonthConfig->maxPerDay ?? 0); - $lastMonthTargetType = intval($lastMonthConfig->targetType ?? 1); - if ($lastMonthTargetType == 1) { - $lastMonthGroups = $this->decodeJsonArray($lastMonthConfig->groups ?? []); - $lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays * count($lastMonthGroups); - } else { - $lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays; - } - } - } - $lastMonthReachRate = $lastMonthPlanned > 0 ? round(($lastMonthSend / $lastMonthPlanned) * 100, 1) : 0; - - // 获取上个月的回复和点击统计(简化处理) - $lastMonthSentItems = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($lastMonthWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - $lastMonthReplyStats = $this->calculateReplyStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd); - $lastMonthClickStats = $this->calculateClickStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd); - - return [ - 'reachRate' => [ - 'value' => $reachRate, - 'trend' => round($reachRate - $lastMonthReachRate, 1), - 'unit' => '%', - 'description' => '成功发送/计划发送' - ], - 'replyRate' => [ - 'value' => $replyStats['replyRate'], - 'trend' => round($replyStats['replyRate'] - $lastMonthReplyStats['replyRate'], 1), - 'unit' => '%', - 'description' => '收到回复/成功发送' - ], - 'avgReplyTime' => [ - 'value' => $replyStats['avgReplyTime'], - 'trend' => round($lastMonthReplyStats['avgReplyTime'] - $replyStats['avgReplyTime'], 0), - 'unit' => '分钟', - 'description' => '从发送到回复的平均时长' - ], - 'clickRate' => [ - 'value' => $clickStats['clickRate'], - 'trend' => round($clickStats['clickRate'] - $lastMonthClickStats['clickRate'], 1), - 'unit' => '%', - 'description' => '点击链接/成功发送' - ], - 'plannedSend' => $plannedSend, - 'successSend' => $successSend, - 'replyCount' => $replyStats['replyCount'], - 'clickCount' => $clickStats['clickCount'] - ]; - } - - /** - * 计算回复统计 - */ - private function calculateReplyStats($sentItems, $targetType, $startTime, $endTime) - { - if (empty($sentItems)) { - return ['replyRate' => 0, 'avgReplyTime' => 0, 'replyCount' => 0]; - } - - $replyCount = 0; - $totalReplyTime = 0; - $replyTimes = []; - - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $sendTime = $itemArray['createTime'] ?? 0; - $accountId = $itemArray['wechatAccountId'] ?? 0; - - if ($targetType == 1) { - // 群推送:查找群内回复消息 - $groupId = $itemArray['groupId'] ?? 0; - $group = Db::name('wechat_group')->where('id', $groupId)->find(); - if ($group) { - $replyMsg = Db::table('s2_wechat_message') - ->where('wechatChatroomId', $group['chatroomId']) - ->where('wechatAccountId', $accountId) - ->where('isSend', 0) // 接收的消息 - ->where('wechatTime', '>', $sendTime) - ->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复 - ->order('wechatTime', 'asc') - ->find(); - - if ($replyMsg) { - $replyCount++; - $replyTime = $replyMsg['wechatTime'] - $sendTime; - $replyTimes[] = $replyTime; - $totalReplyTime += $replyTime; - } - } - } else { - // 好友推送:查找好友回复消息 - $friendId = $itemArray['friendId'] ?? 0; - $friend = Db::table('s2_wechat_friend')->where('id', $friendId)->find(); - if ($friend) { - $replyMsg = Db::table('s2_wechat_message') - ->where('wechatFriendId', $friendId) - ->where('wechatAccountId', $accountId) - ->where('isSend', 0) // 接收的消息 - ->where('wechatTime', '>', $sendTime) - ->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复 - ->order('wechatTime', 'asc') - ->find(); - - if ($replyMsg) { - $replyCount++; - $replyTime = $replyMsg['wechatTime'] - $sendTime; - $replyTimes[] = $replyTime; - $totalReplyTime += $replyTime; - } - } - } - } - - $successSend = count($sentItems); - $replyRate = $successSend > 0 ? round(($replyCount / $successSend) * 100, 1) : 0; - $avgReplyTime = $replyCount > 0 ? round(($totalReplyTime / $replyCount) / 60, 0) : 0; // 转换为分钟 - - return [ - 'replyRate' => $replyRate, - 'avgReplyTime' => $avgReplyTime, - 'replyCount' => $replyCount - ]; - } - - /** - * 计算链接点击统计 - */ - private function calculateClickStats($sentItems, $targetType, $startTime, $endTime) - { - if (empty($sentItems)) { - return ['clickRate' => 0, 'clickCount' => 0]; - } - - $clickCount = 0; - $linkContentIds = []; - - // 获取所有发送的内容ID - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $contentId = $itemArray['contentId'] ?? 0; - if ($contentId > 0) { - $linkContentIds[] = $contentId; - } - } - - if (empty($linkContentIds)) { - return ['clickRate' => 0, 'clickCount' => 0]; - } - - // 查询包含链接的内容 - $linkContents = Db::name('content_item') - ->whereIn('id', array_unique($linkContentIds)) - ->where('contentType', 2) // 链接类型 - ->column('id'); - - // 统计发送了链接内容的记录数 - $linkSendCount = 0; - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $contentId = $itemArray['contentId'] ?? 0; - if (in_array($contentId, $linkContents)) { - $linkSendCount++; - } - } - - // 简化处理:假设点击率基于链接消息的发送(实际应该从点击追踪系统获取) - // 这里可以根据业务需求调整,比如通过消息中的链接点击事件统计 - $clickCount = $linkSendCount; // 简化处理,实际需要真实的点击数据 - - $successSend = count($sentItems); - $clickRate = $successSend > 0 ? round(($clickCount / $successSend) * 100, 1) : 0; - - return [ - 'clickRate' => $clickRate, - 'clickCount' => $clickCount - ]; - } - - /** - * 获取话术组对比数据 - */ - private function getContentLibraryComparison($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - $comparison = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('content_item ci', 'ci.id = wgpi.contentId', 'left') - ->join('content_library cl', 'cl.id = ci.libraryId', 'left') - ->where($queryWhere) - ->where('cl.id', '<>', null) - ->field([ - 'cl.id as libraryId', - 'cl.name as libraryName', - 'COUNT(DISTINCT wgpi.id) as pushCount' - ]) - ->group('cl.id, cl.name') - ->select(); - - $result = []; - foreach ($comparison as $item) { - $libraryId = $item['libraryId']; - $pushCount = intval($item['pushCount']); - - // 获取该内容库的详细统计 - $libraryContentIds = Db::name('content_item') - ->where('libraryId', $libraryId) - ->column('id'); - if (empty($libraryContentIds)) { - $libraryContentIds = [-1]; - } - - $libraryWhere = array_merge($where, [['wgpi.contentId', 'in', $libraryContentIds]]); - $librarySentItems = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($libraryWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - - $config = WorkbenchGroupPush::where('workbenchId', $workbenchId)->find(); - $targetType = $config ? intval($config->targetType) : 1; - - $replyStats = $this->calculateReplyStats($librarySentItems, $targetType, $startTime, $endTime); - $clickStats = $this->calculateClickStats($librarySentItems, $targetType, $startTime, $endTime); - - // 计算转化率(简化处理,实际需要根据业务定义) - $conversionRate = $pushCount > 0 ? round(($replyStats['replyCount'] / $pushCount) * 100, 1) : 0; - - $result[] = [ - 'libraryId' => $libraryId, - 'libraryName' => $item['libraryName'], - 'pushCount' => $pushCount, - 'reachRate' => 100, // 简化处理,实际应该计算 - 'replyRate' => $replyStats['replyRate'], - 'clickRate' => $clickStats['clickRate'], - 'conversionRate' => $conversionRate, - 'avgReplyTime' => $replyStats['avgReplyTime'], - 'level' => $this->getPerformanceLevel($replyStats['replyRate'], $clickStats['clickRate'], $conversionRate) - ]; - } - - // 按回复率排序 - usort($result, function($a, $b) { - return $b['replyRate'] <=> $a['replyRate']; - }); - - return $result; - } - - /** - * 获取性能等级 - */ - private function getPerformanceLevel($replyRate, $clickRate, $conversionRate) - { - $score = ($replyRate * 0.4) + ($clickRate * 0.3) + ($conversionRate * 0.3); - - if ($score >= 40) { - return '优秀'; - } elseif ($score >= 25) { - return '良好'; - } elseif ($score >= 15) { - return '一般'; - } else { - return '待提升'; - } - } - - /** - * 获取时段分析数据 - */ - private function getTimePeriodAnalysis($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - $analysis = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->field([ - 'FROM_UNIXTIME(wgpi.createTime, "%H") as hour', - 'COUNT(*) as count' - ]) - ->group('hour') - ->order('hour', 'asc') - ->select(); - - $result = []; - foreach ($analysis as $item) { - $result[] = [ - 'hour' => intval($item['hour']), - 'count' => intval($item['count']) - ]; - } - - return $result; - } - - /** - * 获取互动深度数据 - */ - private function getInteractionDepth($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - // 简化处理,实际需要更复杂的统计逻辑 - return [ - 'singleReply' => 0, // 单次回复 - 'multipleReply' => 0, // 多次回复 - 'deepInteraction' => 0 // 深度互动 - ]; - } - - /** - * 获取推送历史记录列表 - * @return \think\response\Json - */ - public function getGroupPushHistory() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchGroupPushController(); - $controller->request = $this->request; - return $controller->getGroupPushHistory(); - } - - /** - * 获取状态文本 - * @param string $status 状态码 - * @return string 状态文本 - */ - private function getStatusText($status) - { - $statusMap = [ - 'success' => '已完成', - 'partial' => '进行中', - 'pending' => '进行中', - 'failed' => '失败' - ]; - return $statusMap[$status] ?? '未知'; - } - - /** - * 获取已创建的群列表(自动建群) - * @return \think\response\Json - */ - public function getCreatedGroupsList() - { - $workbenchId = $this->request->param('workbenchId', 0); - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 100); - $keyword = $this->request->param('keyword', ''); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 获取已创建的群ID列表(状态为成功且groupId不为空) - $groupIds = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->where('groupId', '<>', null) - ->group('groupId') - ->column('groupId'); - - if (empty($groupIds)) { - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => [], - 'total' => 0, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - // 查询群组详细信息(从s2_wechat_chatroom表查询) - $query = Db::table('s2_wechat_chatroom')->alias('wc') - ->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left') - ->where('wc.id', 'in', $groupIds) - ->where('wc.isDeleted', 0); - - // 关键字搜索 - if (!empty($keyword)) { - $query->where(function ($q) use ($keyword) { - $q->where('wc.nickname', 'like', '%' . $keyword . '%') - ->whereOr('wc.chatroomId', 'like', '%' . $keyword . '%') - ->whereOr('wa.nickName', 'like', '%' . $keyword . '%'); - }); - } - - $total = $query->count(); - $list = $query->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias') - ->order('wc.createTime', 'desc') - ->page($page, $limit) - ->select(); - - // 统计每个群的成员数量和成员信息 - foreach ($list as &$item) { - // 统计该群的成员数量(从workbench_group_create_item表统计) - $memberCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $item['id']) - ->where('status', 'in', [2, 4]) // 创建成功和管理员好友已拉入 - ->count(); - - // 获取成员列表(用于显示成员头像) - $memberList = Db::name('workbench_group_create_item')->alias('wgci') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left') - ->where('wgci.workbenchId', $workbenchId) - ->where('wgci.groupId', $item['id']) - ->where('wgci.status', 'in', [2, 4]) - ->field('wf.avatar, wf.wechatId, wf.nickname') - ->order('wgci.createTime', 'asc') - ->limit(10) // 最多显示10个成员头像 - ->select(); - - // 格式化成员头像列表 - $memberAvatars = []; - foreach ($memberList as $member) { - if (!empty($member['avatar'])) { - $memberAvatars[] = [ - 'avatar' => $member['avatar'], - 'wechatId' => $member['wechatId'] ?? '', - 'nickname' => $member['nickname'] ?? '' - ]; - } - } - - // 计算剩余成员数(用于显示"+XX") - $remainingCount = $memberCount > count($memberAvatars) ? $memberCount - count($memberAvatars) : 0; - - // 格式化返回数据 - $item['memberCount'] = $memberCount; - $item['memberCountText'] = $memberCount . '人'; // 格式化为"XX人" - $item['createTime'] = !empty($item['createTime']) ? date('Y-m-d', $item['createTime']) : ''; // 格式化为"YYYY-MM-DD" - $item['memberAvatars'] = $memberAvatars; // 成员头像列表(最多10个) - $item['remainingCount'] = $remainingCount; // 剩余成员数(用于显示"+XX") - - // 保留原有字段,但调整格式 - $item['groupName'] = $item['groupName'] ?? ''; - $item['groupAvatar'] = $item['groupAvatar'] ?? ''; - } - unset($item); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取已创建群的详情(自动建群) - * @return \think\response\Json - */ - public function getCreatedGroupDetail() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 验证该群是否属于该工作台 - $groupItem = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $groupId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->find(); - - if (empty($groupItem)) { - return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']); - } - - // 查询群基本信息(从s2_wechat_chatroom表查询) - $group = Db::table('s2_wechat_chatroom')->alias('wc') - ->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left') - ->where('wc.id', $groupId) - ->where('wc.isDeleted', 0) - ->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias,wc.announce') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - // 获取chatroomId - $chatroomId = $group['chatroomId'] ?? ''; - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - // 从s2_wechat_chatroom_member表查询所有成员列表(不限制数量) - $memberList = Db::table('s2_wechat_chatroom_member')->alias('wcm') - ->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = wcm.wechatId', 'left') - ->where('wcm.chatroomId', $chatroomId) - ->field('wcm.wechatId,wcm.nickname as memberNickname,wcm.avatar as memberAvatar,wcm.conRemark as memberRemark,wcm.alias as memberAlias,wcm.createTime as joinTime,wcm.updateTime,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar') - ->order('wcm.createTime', 'asc') - ->select(); - - // 去重:按wechatId去重,保留第一条记录 - $memberMap = []; - foreach ($memberList as $member) { - $wechatId = $member['wechatId'] ?? ''; - if (!empty($wechatId) && !isset($memberMap[$wechatId])) { - $memberMap[$wechatId] = $member; - } - } - $memberList = array_values($memberMap); // 重新索引数组 - - // 获取在群中的成员wechatId列表(用于判断是否已退群) - $inGroupWechatIds = array_column($memberList, 'wechatId'); - $inGroupWechatIds = array_filter($inGroupWechatIds); // 过滤空值 - - // 获取通过自动建群加入的成员信息(用于判断入群状态和已退群成员) - $autoJoinMemberList = Db::name('workbench_group_create_item') - ->alias('wgci') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left') - ->where('wgci.workbenchId', $workbenchId) - ->where('wgci.groupId', $groupId) - ->where('wgci.status', 'in', [2, 4]) // 创建成功和管理员好友已拉入 - ->field('wf.wechatId,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar,wgci.createTime as autoJoinTime') - ->select(); - - // 去重:按wechatId去重,保留第一条记录 - $autoJoinMemberMap = []; - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - if (!empty($wechatId) && !isset($autoJoinMemberMap[$wechatId])) { - $autoJoinMemberMap[$wechatId] = $autoMember; - } - } - $autoJoinMemberList = array_values($autoJoinMemberMap); // 重新索引数组 - - // 统计成员总数(包括在群中的成员和已退群的成员) - // 先统计在群中的成员数 - $inGroupCount = count($memberList); - - // 统计已退群的成员数(在自动建群记录中但不在群中的成员) - $quitCount = 0; - if (!empty($autoJoinMemberList)) { - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - if (!empty($wechatId) && !in_array($wechatId, $inGroupWechatIds)) { - $quitCount++; - } - } - } - - // 总成员数 = 在群中的成员数 + 已退群的成员数 - $memberCount = $inGroupCount + $quitCount; - - // 获取自动建群加入的成员wechatId列表(用于判断入群状态) - $autoJoinWechatIds = array_column($autoJoinMemberList, 'wechatId'); - $autoJoinWechatIds = array_filter($autoJoinWechatIds); // 过滤空值 - - // 格式化在群中的成员列表 - $members = []; - $addedWechatIds = []; // 用于记录已添加的成员wechatId,避免重复 - $ownerWechatId = $group['ownerWechatId'] ?? ''; - foreach ($memberList as $member) { - $wechatId = $member['wechatId'] ?? ''; - - // 跳过空wechatId和已添加的成员 - if (empty($wechatId) || in_array($wechatId, $addedWechatIds)) { - continue; - } - - // 标记为已添加 - $addedWechatIds[] = $wechatId; - - // 根据wechatId判断是否为群主 - $isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0; - - // 判断入群状态:如果在自动建群记录中,说明是通过自动建群加入的;否则是其他方式加入的 - $joinStatus = in_array($wechatId, $autoJoinWechatIds) ? 'auto' : 'manual'; - - // 判断是否已退群:如果成员在s2_wechat_chatroom_member表中存在,说明在群中;否则已退群 - // 由于我们已经从s2_wechat_chatroom_member表查询,所以这里都是"在群中"状态 - $isQuit = 0; // 0=在群中,1=已退群 - - // 优先使用friend表的昵称和头像,如果没有则使用member表的 - $nickname = !empty($member['friendNickname']) ? $member['friendNickname'] : ($member['memberNickname'] ?? ''); - $avatar = !empty($member['friendAvatar']) ? $member['friendAvatar'] : ($member['memberAvatar'] ?? ''); - - $members[] = [ - 'friendId' => $member['friendId'] ?? 0, - 'wechatId' => $wechatId, - 'nickname' => $nickname, - 'avatar' => $avatar, - 'alias' => $member['memberAlias'] ?? '', - 'remark' => $member['memberRemark'] ?? '', - 'isOwner' => $isOwner, // 标记群主 - 'joinStatus' => $joinStatus, // 入群状态:auto=自动建群加入,manual=其他方式加入 - 'isQuit' => $isQuit, // 是否已退群:0=在群中,1=已退群 - 'joinTime' => !empty($member['joinTime']) ? date('Y-m-d H:i:s', $member['joinTime']) : '', // 入群时间 - ]; - } - - // 添加已退群的成员(在自动建群记录中但不在群中的成员) - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - - // 跳过空wechatId、已在群中的成员和已添加的成员 - if (empty($wechatId) || in_array($wechatId, $inGroupWechatIds) || in_array($wechatId, $addedWechatIds)) { - continue; - } - - // 标记为已添加 - $addedWechatIds[] = $wechatId; - - // 根据wechatId判断是否为群主 - $isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0; - - $members[] = [ - 'friendId' => $autoMember['friendId'] ?? 0, - 'wechatId' => $wechatId, - 'nickname' => $autoMember['friendNickname'] ?? '', - 'avatar' => $autoMember['friendAvatar'] ?? '', - 'alias' => '', - 'remark' => '', - 'isOwner' => $isOwner, // 标记群主 - 'joinStatus' => 'auto', // 入群状态:auto=自动建群加入 - 'isQuit' => 1, // 是否已退群:1=已退群 - 'joinTime' => !empty($autoMember['autoJoinTime']) ? date('Y-m-d H:i:s', $autoMember['autoJoinTime']) : '', // 入群时间 - ]; - } - - // 将群主排在第一位 - usort($members, function($a, $b) { - if ($a['isOwner'] == $b['isOwner']) { - return 0; - } - return $a['isOwner'] > $b['isOwner'] ? -1 : 1; - }); - - // 格式化返回数据 - $result = [ - 'id' => $group['id'], - 'groupName' => $group['groupName'] ?? '', - 'chatroomId' => $group['chatroomId'] ?? '', - 'groupAvatar' => $group['groupAvatar'] ?? '', - 'ownerWechatId' => $group['ownerWechatId'] ?? '', - 'ownerNickname' => $group['ownerNickname'] ?? '', - 'ownerAvatar' => $group['ownerAvatar'] ?? '', - 'ownerAlias' => $group['ownerAlias'] ?? '', - 'announce' => $group['announce'] ?? '', - 'createTime' => !empty($group['createTime']) ? date('Y-m-d H:i', $group['createTime']) : '', // 格式化为"YYYY-MM-DD HH:MM" - 'memberCount' => $memberCount, - 'memberCountText' => $memberCount . '人', // 格式化为"XX人" - 'workbenchName' => $workbench->name ?? '', // 任务名称(工作台名称) - 'members' => $members // 所有成员列表 - ]; - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => $result - ]); - } - - /** - * 同步群最新信息(包括群成员) - * @return \think\response\Json - */ - public function syncGroupInfo() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 验证该群是否属于该工作台 - $groupItem = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $groupId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->find(); - - if (empty($groupItem)) { - return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']); - } - - // 查询群基本信息,获取chatroomId和wechatAccountWechatId - $group = Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->where('isDeleted', 0) - ->field('id,chatroomId,wechatAccountWechatId') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - $chatroomId = $group['chatroomId'] ?? ''; - $wechatAccountWechatId = $group['wechatAccountWechatId'] ?? ''; - - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - try { - // 实例化WechatChatroomController - $chatroomController = new \app\api\controller\WechatChatroomController(); - - // 1. 同步群信息(调用getlist方法) - $syncData = [ - 'wechatChatroomId' => $chatroomId, // 使用chatroomId作为wechatChatroomId来指定要同步的群 - 'wechatAccountKeyword' => $wechatAccountWechatId, // 通过群主微信ID筛选 - 'isDeleted' => false, - 'pageIndex' => 1, - 'pageSize' => 100 // 获取足够多的数据 - ]; - $syncResult = $chatroomController->getlist($syncData, true, 0); // isInner = true - $syncResponse = json_decode($syncResult, true); - - if (empty($syncResponse['code']) || $syncResponse['code'] != 200) { - return json(['code' => 500, 'msg' => '同步群信息失败:' . ($syncResponse['msg'] ?? '未知错误')]); - } - - // 2. 同步群成员信息(调用listChatroomMember方法) - // wechatChatroomId 使用 s2_wechat_chatroom 表的 id(即groupId) - // chatroomId 使用群聊ID(chatroomId) - $wechatChatroomId = $groupId; // s2_wechat_chatroom表的id - $memberSyncResult = $chatroomController->listChatroomMember($wechatChatroomId, $chatroomId, true); // isInner = true - $memberSyncResponse = json_decode($memberSyncResult, true); - - if (empty($memberSyncResponse['code']) || $memberSyncResponse['code'] != 200) { - // 成员同步失败不影响整体结果,记录警告即可 - \think\facade\Log::warning("同步群成员失败。群ID: {$groupId}, 群聊ID: {$chatroomId}, 错误: " . ($memberSyncResponse['msg'] ?? '未知错误')); - } - - return json([ - 'code' => 200, - 'msg' => '同步成功', - 'data' => [ - 'groupId' => $groupId, - 'chatroomId' => $chatroomId, - 'groupInfoSynced' => true, - 'memberInfoSynced' => !empty($memberSyncResponse['code']) && $memberSyncResponse['code'] == 200 - ] - ]); - } catch (\Exception $e) { - \think\facade\Log::error("同步群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage()); - return json(['code' => 500, 'msg' => '同步失败:' . $e->getMessage()]); - } - } - - /** - * 修改群名称、群公告 - * @return \think\response\Json - */ - public function modifyGroupInfo() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - $chatroomName = $this->request->param('chatroomName', ''); - $announce = $this->request->param('announce', ''); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 至少需要提供一个修改项 - if (empty($chatroomName) && empty($announce)) { - return json(['code' => 400, 'msg' => '请至少提供群名称或群公告中的一个参数']); - } - - // 查询群基本信息 - $group = Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->where('isDeleted', 0) - ->field('id,chatroomId,wechatAccountWechatId,accountId,wechatAccountId') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - $chatroomId = $group['id'] ?? ''; - - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - try { - // 直接使用群表中的账号信息 - $executeAccountId = $group['accountId'] ?? 0; - $executeWechatAccountId = $group['wechatAccountId'] ?? 0; - $executeWechatId = $group['wechatAccountWechatId'] ?? ''; - - // 确保 wechatId 不为空 - if (empty($executeWechatId)) { - return json(['code' => 400, 'msg' => '无法获取微信账号ID']); - } - - // 调用 WebSocketController 修改群信息 - // 获取系统API账号信息(用于WebSocket连接) - $username = Env::get('api.username2', ''); - $password = Env::get('api.password2', ''); - - if (empty($username) || empty($password)) { - return json(['code' => 500, 'msg' => '系统API账号配置缺失']); - } - - // 获取系统账号ID - $systemAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); - if (empty($systemAccountId)) { - return json(['code' => 500, 'msg' => '未找到系统账号ID']); - } - - $webSocketController = new WebSocketController([ - 'userName' => $username, - 'password' => $password, - 'accountId' => $systemAccountId - ]); - // 构建修改参数 - $modifyData = [ - 'wechatChatroomId' => $chatroomId, - 'wechatAccountId' => $executeWechatAccountId, - ]; - if (!empty($chatroomName)) { - $modifyData['chatroomName'] = $chatroomName; - } - if (!empty($announce)) { - $modifyData['announce'] = $announce; - } - - - $modifyResult = $webSocketController->CmdChatroomModifyInfo($modifyData); - $modifyResponse = json_decode($modifyResult, true); - if (empty($modifyResponse['code']) || $modifyResponse['code'] != 200) { - return json(['code' => 500, 'msg' => '修改群信息失败:' . ($modifyResponse['msg'] ?? '未知错误')]); - } - - // 修改成功后更新数据库 - $updateData = [ - 'updateTime' => time() - ]; - - // 如果修改了群名称,更新数据库 - if (!empty($chatroomName)) { - $updateData['nickname'] = $chatroomName; - } - - // 如果修改了群公告,更新数据库 - if (!empty($announce)) { - $updateData['announce'] = $announce; - } - - // 更新数据库 - Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->update($updateData); - - return json([ - 'code' => 200, - 'msg' => '修改成功', - 'data' => [ - 'groupId' => $groupId, - 'chatroomId' => $chatroomId, - 'chatroomName' => $chatroomName, - 'announce' => $announce - ] - ]); - } catch (\Exception $e) { - \think\facade\Log::error("修改群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage()); - return json(['code' => 500, 'msg' => '修改失败:' . $e->getMessage()]); - } - } - - /** - * 转移群聊到指定账号 - * @param int $groupId 群ID(s2_wechat_chatroom表的id) - * @param string $chatroomId 群聊ID - * @param int $toAccountId 目标账号ID(s2_company_account表的id) - * @param int $toWechatAccountId 目标微信账号ID(s2_wechat_account表的id) - * @return array ['success' => bool, 'msg' => string] - */ - protected function transferChatroomToAccount($groupId, $chatroomId, $toAccountId, $toWechatAccountId) - { - try { - // 查询目标账号信息 - $targetAccount = Db::table('s2_company_account') - ->where('id', $toAccountId) - ->field('id,userName,realName,nickname') - ->find(); - - if (empty($targetAccount)) { - return ['success' => false, 'msg' => '目标账号不存在']; - } - - // 查询目标微信账号信息 - $targetWechatAccount = Db::table('s2_wechat_account') - ->where('id', $toWechatAccountId) - ->field('id,wechatId,deviceAccountId') - ->find(); - - if (empty($targetWechatAccount)) { - return ['success' => false, 'msg' => '目标微信账号不存在']; - } - - // 调用 AutomaticAssign 进行群聊转移 - $automaticAssign = new \app\api\controller\AutomaticAssign(); - - // 构建转移参数(通过 API 调用) - $transferData = [ - 'wechatChatroomId' => $chatroomId, // 使用群聊ID - 'toAccountId' => $toAccountId, - 'wechatAccountKeyword' => $targetWechatAccount['wechatId'] ?? '', - 'isDeleted' => false - ]; - - // 直接更新数据库(因为 API 可能不支持指定单个群聊ID转移) - // 更新 s2_wechat_chatroom 表的 accountId 和 wechatAccountId - Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->update([ - 'accountId' => $toAccountId, - 'accountUserName' => $targetAccount['userName'] ?? '', - 'accountRealName' => $targetAccount['realName'] ?? '', - 'accountNickname' => $targetAccount['nickname'] ?? '', - 'wechatAccountId' => $toWechatAccountId, - 'wechatAccountWechatId' => $targetWechatAccount['wechatId'] ?? '', - 'updateTime' => time() - ]); - - return ['success' => true, 'msg' => '转移成功']; - } catch (\Exception $e) { - \think\facade\Log::error("转移群聊异常。群ID: {$groupId}, 目标账号ID: {$toAccountId}, 错误: " . $e->getMessage()); - return ['success' => false, 'msg' => $e->getMessage()]; - } - } - - -} \ No newline at end of file diff --git a/Server/application/cunkebao/controller/workbench/WorkbenchGroupCreateController.php b/Server/application/cunkebao/controller/workbench/WorkbenchGroupCreateController.php index 8057731b..f1063a5d 100644 --- a/Server/application/cunkebao/controller/workbench/WorkbenchGroupCreateController.php +++ b/Server/application/cunkebao/controller/workbench/WorkbenchGroupCreateController.php @@ -2,12 +2,13 @@ namespace app\cunkebao\controller\workbench; -use app\cunkebao\model\Workbench; use think\Controller; -use think\Db; /** * 工作台 - 自动建群相关功能 + * + * 注意:此控制器中的方法已迁移至 WorkbenchController + * 保留此文件仅用于类型常量定义,实际路由指向 WorkbenchController */ class WorkbenchGroupCreateController extends Controller { @@ -15,3881 +16,4 @@ class WorkbenchGroupCreateController extends Controller * 工作台类型定义 */ const TYPE_GROUP_CREATE = 4; // 自动建群 - - /** - * 创建工作台 - * @return \think\response\Json - */ - public function create() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - // 获取登录用户信息 - $userInfo = request()->userInfo; - - // 获取请求参数 - $param = $this->request->post(); - - - // 根据业务默认值补全参数 - if ( - isset($param['type']) && - intval($param['type']) === self::TYPE_GROUP_PUSH - ) { - if (empty($param['startTime'])) { - $param['startTime'] = '09:00'; - } - if (empty($param['endTime'])) { - $param['endTime'] = '21:00'; - } - } - - // 验证数据 - $validate = new WorkbenchValidate; - if (!$validate->scene('create')->check($param)) { - return json(['code' => 400, 'msg' => $validate->getError()]); - } - - Db::startTrans(); - try { - // 创建工作台基本信息 - $workbench = new Workbench; - $workbench->name = $param['name']; - $workbench->type = $param['type']; - $workbench->status = !empty($param['status']) ? 1 : 0; - $workbench->autoStart = !empty($param['autoStart']) ? 1 : 0; - $workbench->userId = $userInfo['id']; - $workbench->companyId = $userInfo['companyId']; - $workbench->createTime = time(); - $workbench->updateTime = time(); - $workbench->save(); - - // 根据类型创建对应的配置 - switch ($param['type']) { - case self::TYPE_AUTO_LIKE: // 自动点赞 - $config = new WorkbenchAutoLike; - $config->workbenchId = $workbench->id; - $config->interval = $param['interval']; - $config->maxLikes = $param['maxLikes']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->contentTypes = json_encode($param['contentTypes']); - $config->devices = json_encode($param['deviceGroups']); - $config->friends = json_encode($param['wechatFriends']); - // $config->targetGroups = json_encode($param['targetGroups']); - // $config->tagOperator = $param['tagOperator']; - $config->friendMaxLikes = $param['friendMaxLikes']; - $config->friendTags = $param['friendTags']; - $config->enableFriendTags = $param['enableFriendTags']; - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_MOMENTS_SYNC: // 朋友圈同步 - $config = new WorkbenchMomentsSync; - $config->workbenchId = $workbench->id; - $config->syncInterval = $param['syncInterval']; - $config->syncCount = $param['syncCount']; - $config->syncType = $param['syncType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->accountType = $param['accountType']; - $config->devices = json_encode($param['deviceGroups']); - $config->contentLibraries = json_encode($param['contentGroups'] ?? []); - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_GROUP_PUSH: // 群消息推送 - $ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? []); - $groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds); - $groupPushData['workbenchId'] = $workbench->id; - $groupPushData['createTime'] = time(); - $groupPushData['updateTime'] = time(); - $config = new WorkbenchGroupPush; - $config->save($groupPushData); - break; - case self::TYPE_GROUP_CREATE: // 自动建群 - $config = new WorkbenchGroupCreate; - $config->workbenchId = $workbench->id; - $config->planType = !empty($param['planType']) ? $param['planType'] : 0; - $config->executorId = !empty($param['executorId']) ? $param['executorId'] : 0; - - $config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->startTime = $param['startTime'] ?? ''; - $config->endTime = $param['endTime'] ?? ''; - $config->groupSizeMin = intval($param['groupSizeMin'] ?? 3); - $config->groupSizeMax = intval($param['groupSizeMax'] ?? 38); - $config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20); - $config->groupNameTemplate = $param['groupNameTemplate'] ?? ''; - $config->groupDescription = $param['groupDescription'] ?? ''; - $config->poolGroups = json_encode($param['poolGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->wechatGroups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE); - - // 处理群管理员:如果启用了群管理员且有指定管理员,则保存到admins字段 - $admins = []; - if (!empty($param['groupAdminEnabled']) && !empty($param['groupAdminWechatId'])) { - // 如果groupAdminWechatId是数组,取第一个;如果是单个值,直接使用 - $adminWechatId = is_array($param['groupAdminWechatId']) ? $param['groupAdminWechatId'][0] : $param['groupAdminWechatId']; - // 如果是好友ID,直接添加到admins;如果是wechatId,需要转换为好友ID - if (is_numeric($adminWechatId)) { - $admins[] = intval($adminWechatId); - } else { - // 如果是wechatId字符串,需要查询对应的好友ID - $friend = Db::table('s2_wechat_friend')->where('wechatId', $adminWechatId)->find(); - if ($friend) { - $admins[] = intval($friend['id']); - } - } - } - // 如果传入了admins参数,优先使用(兼容旧逻辑) - if (!empty($param['admins']) && is_array($param['admins'])) { - $admins = array_merge($admins, $param['admins']); - } - $config->admins = json_encode(array_unique($admins), JSON_UNESCAPED_UNICODE); - - $config->fixedWechatIds = json_encode($param['fixedWechatIds'] ?? [], JSON_UNESCAPED_UNICODE); - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_TRAFFIC_DISTRIBUTION: // 流量分发 - $config = new WorkbenchTrafficConfig; - $config->workbenchId = $workbench->id; - $config->distributeType = $param['distributeType']; - $config->maxPerDay = $param['maxPerDay']; - $config->timeType = $param['timeType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->devices = json_encode($param['deviceGroups'], JSON_UNESCAPED_UNICODE); - $config->pools = json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE); - $config->account = json_encode($param['accountGroups'], JSON_UNESCAPED_UNICODE); - $config->createTime = time(); - $config->updateTime = time(); - $config->save(); - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = new WorkbenchImportContact; - $config->workbenchId = $workbench->id; - $config->devices = json_encode($param['deviceGroups'], JSON_UNESCAPED_UNICODE); - $config->pools = json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE); - $config->num = $param['num']; - $config->clearContact = $param['clearContact']; - $config->remark = $param['remark']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->createTime = time(); - $config->save(); - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '创建成功', 'data' => ['id' => $workbench->id]]); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]); - } - } - - /** - * 获取工作台列表 - * @return \think\response\Json - */ - public function getList() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $type = $this->request->param('type', ''); - $keyword = $this->request->param('keyword', ''); - - $where = [ - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - - - // 添加类型筛选 - if ($type !== '') { - $where[] = ['type', '=', $type]; - } - - // 添加名称模糊搜索 - if ($keyword !== '') { - $where[] = ['name', 'like', '%' . $keyword . '%']; - } - - // 定义关联关系 - $with = [ - 'autoLike' => function ($query) { - $query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends'); - }, - 'momentsSync' => function ($query) { - $query->field('workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries'); - }, - 'trafficConfig' => function ($query) { - $query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account'); - }, - 'groupPush' => function ($query) { - $query->field('workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt'); - }, - 'groupCreate' => function ($query) { - $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins'); - }, - 'importContact' => function ($query) { - $query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime'); - }, - 'user' => function ($query) { - $query->field('id,username'); - } - ]; - - $list = Workbench::where($where) - ->with($with) - ->field('id,companyId,name,type,status,autoStart,userId,createTime,updateTime') - ->order('id', 'desc') - ->page($page, $limit) - ->select() - ->each(function ($item) { - // 处理配置信息 - switch ($item->type) { - case self::TYPE_AUTO_LIKE: - if (!empty($item->autoLike)) { - $item->config = $item->autoLike; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->contentTypes = json_decode($item->config->contentTypes, true); - $item->config->friends = json_decode($item->config->friends, true); - - // 添加今日点赞数 - $startTime = strtotime(date('Y-m-d') . ' 00:00:00'); - $endTime = strtotime(date('Y-m-d') . ' 23:59:59'); - $todayLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $item->id) - ->whereTime('createTime', 'between', [$startTime, $endTime]) - ->count(); - - // 添加总点赞数 - $totalLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $item->id) - ->count(); - - $item->config->todayLikeCount = $todayLikeCount; - $item->config->totalLikeCount = $totalLikeCount; - } - unset($item->autoLike, $item->auto_like); - break; - case self::TYPE_MOMENTS_SYNC: - if (!empty($item->momentsSync)) { - $item->config = $item->momentsSync; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->contentGroups = json_decode($item->config->contentLibraries, true); - //同步记录 - $sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->count(); - $item->syncCount = $sendNum; - $lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $item->id])->order('id DESC')->value('createTime'); - $item->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--'; - - - // 获取内容库名称 - if (!empty($item->config->contentGroups)) { - $libraryNames = ContentLibrary::where('id', 'in', $item->config->contentGroups)->select(); - $item->config->contentGroupsOptions = $libraryNames; - } else { - $item->config->contentGroupsOptions = []; - } - } - unset($item->momentsSync, $item->moments_sync, $item->config->contentLibraries); - break; - case self::TYPE_GROUP_PUSH: - if (!empty($item->groupPush)) { - $item->config = $item->groupPush; - $item->config->pushType = $item->config->pushType; - $item->config->targetType = isset($item->config->targetType) ? intval($item->config->targetType) : 1; // 默认1=群推送 - $item->config->groupPushSubType = isset($item->config->groupPushSubType) ? intval($item->config->groupPushSubType) : 1; // 默认1=群群发 - $item->config->startTime = $item->config->startTime; - $item->config->endTime = $item->config->endTime; - $item->config->maxPerDay = $item->config->maxPerDay; - $item->config->pushOrder = $item->config->pushOrder; - $item->config->isLoop = $item->config->isLoop; - $item->config->status = $item->config->status; - $item->config->ownerWechatIds = json_decode($item->config->ownerWechatIds ?? '[]', true) ?: []; - // 根据targetType解析不同的数据 - if ($item->config->targetType == 1) { - // 群推送 - $item->config->wechatGroups = json_decode($item->config->groups, true) ?: []; - $item->config->wechatFriends = []; - // 群推送不需要devices字段 - // 群公告相关字段 - if ($item->config->groupPushSubType == 2) { - $item->config->announcementContent = isset($item->config->announcementContent) ? $item->config->announcementContent : ''; - $item->config->enableAiRewrite = isset($item->config->enableAiRewrite) ? intval($item->config->enableAiRewrite) : 0; - $item->config->aiRewritePrompt = isset($item->config->aiRewritePrompt) ? $item->config->aiRewritePrompt : ''; - } - $item->config->trafficPools = []; - } else { - // 好友推送 - $item->config->wechatFriends = json_decode($item->config->friends, true) ?: []; - $item->config->wechatGroups = []; - $item->config->trafficPools = json_decode($item->config->trafficPools ?? '[]', true) ?: []; - } - $item->config->contentLibraries = json_decode($item->config->contentLibraries, true); - $item->config->postPushTags = json_decode($item->config->postPushTags ?? '[]', true) ?: []; - $item->config->lastPushTime = ''; - if (!empty($item->config->ownerWechatIds)) { - $ownerWechatOptions = Db::name('wechat_account') - ->whereIn('id', $item->config->ownerWechatIds) - ->field('id,wechatId,nickName,avatar,alias') - ->select(); - $item->config->ownerWechatOptions = $ownerWechatOptions; - } else { - $item->config->ownerWechatOptions = []; - } - } - unset($item->groupPush, $item->group_push); - break; - case self::TYPE_GROUP_CREATE: - if (!empty($item->groupCreate)) { - $item->config = $item->groupCreate; - $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) ?: []; - - // 处理群管理员相关字段 - $item->config->groupAdminEnabled = !empty($item->config->admins) ? 1 : 0; - - 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; - // 如果有管理员,设置groupAdminWechatId为第一个管理员的ID(用于前端回显) - $item->config->groupAdminWechatId = !empty($item->config->admins) ? $item->config->admins[0] : null; - } else { - $item->config->adminsOptions = []; - $item->config->groupAdminWechatId = null; - } - } - unset($item->groupCreate, $item->group_create); - break; - case self::TYPE_TRAFFIC_DISTRIBUTION: - if (!empty($item->trafficConfig)) { - $item->config = $item->trafficConfig; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->poolGroups = json_decode($item->config->pools, true); - $item->config->account = json_decode($item->config->account, true); - $config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $item->id])->order('id DESC')->find(); - $item->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--'; - - //统计 - $labels = $item->config->poolGroups; - $totalUsers = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where([ - ['wf.isDeleted', '=', 0], - ['sa.departmentId', '=', $item->companyId] - ]) - ->whereIn('wa.currentDeviceId', $item->config->devices); - - if (!empty($labels) && count($labels) > 0) { - $totalUsers = $totalUsers->where(function ($q) use ($labels) { - foreach ($labels as $label) { - $q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')"); - } - }); - } - - $totalUsers = $totalUsers->count(); - $totalAccounts = count($item->config->account); - $dailyAverage = Db::name('workbench_traffic_config_item') - ->where('workbenchId', $item->id) - ->count(); - $day = (time() - strtotime($item->createTime)) / 86400; - $day = intval($day); - if ($dailyAverage > 0 && $totalAccounts > 0 && $day > 0) { - $dailyAverage = $dailyAverage / $totalAccounts / $day; - } - $item->config->total = [ - 'dailyAverage' => intval($dailyAverage), - 'totalAccounts' => $totalAccounts, - 'deviceCount' => count($item->config->devices), - 'poolCount' => !empty($item->config->poolGroups) ? count($item->config->poolGroups) : 'ALL', - 'totalUsers' => $totalUsers >> 0 - ]; - } - unset($item->trafficConfig, $item->traffic_config); - break; - - case self::TYPE_IMPORT_CONTACT: - if (!empty($item->importContact)) { - $item->config = $item->importContact; - $item->config->devices = json_decode($item->config->devices, true); - $item->config->poolGroups = json_decode($item->config->pools, true); - } - unset($item->importContact, $item->import_contact); - break; - } - // 添加创建人名称 - $item['creatorName'] = $item->user ? $item->user->username : ''; - unset($item['user']); // 移除关联数据 - return $item; - }); - - $total = Workbench::where($where)->count(); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取工作台详情 - * @param int $id 工作台ID - * @return \think\response\Json - */ - public function detail() - { - $id = $this->request->param('id', ''); - - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 定义关联关系 - $with = [ - 'autoLike' => function ($query) { - $query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends,friendMaxLikes,friendTags,enableFriendTags'); - }, - 'momentsSync' => function ($query) { - $query->field('workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries'); - }, - 'trafficConfig' => function ($query) { - $query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account'); - }, - 'groupPush' => function ($query) { - $query->field('workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt'); - }, - 'groupCreate' => function ($query) { - $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins'); - }, - 'importContact' => function ($query) { - $query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime'); - }, - ]; - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - - $workbench = Workbench::where($where) - ->field('id,name,type,status,autoStart,createTime,updateTime,companyId') - ->with($with) - ->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - // 处理配置信息 - switch ($workbench->type) { - //自动点赞 - case self::TYPE_AUTO_LIKE: - if (!empty($workbench->autoLike)) { - $workbench->config = $workbench->autoLike; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->wechatFriends = json_decode($workbench->config->friends, true); - $workbench->config->targetType = 2; - //$workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true); - $workbench->config->contentTypes = json_decode($workbench->config->contentTypes, true); - - // 添加今日点赞数 - $startTime = strtotime(date('Y-m-d') . ' 00:00:00'); - $endTime = strtotime(date('Y-m-d') . ' 23:59:59'); - $todayLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $workbench->id) - ->whereTime('createTime', 'between', [$startTime, $endTime]) - ->count(); - - // 添加总点赞数 - $totalLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $workbench->id) - ->count(); - - $workbench->config->todayLikeCount = $todayLikeCount; - $workbench->config->totalLikeCount = $totalLikeCount; - - unset($workbench->autoLike, $workbench->auto_like); - } - break; - //自动同步朋友圈 - case self::TYPE_MOMENTS_SYNC: - if (!empty($workbench->momentsSync)) { - $workbench->config = $workbench->momentsSync; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->contentGroups = json_decode($workbench->config->contentLibraries, true); - - //同步记录 - $sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->count(); - $workbench->syncCount = $sendNum; - $lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->value('createTime'); - $workbench->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--'; - unset($workbench->momentsSync, $workbench->moments_sync); - } - break; - //群推送 - case self::TYPE_GROUP_PUSH: - if (!empty($workbench->groupPush)) { - $workbench->config = $workbench->groupPush; - $workbench->config->targetType = isset($workbench->config->targetType) ? intval($workbench->config->targetType) : 1; // 默认1=群推送 - $workbench->config->groupPushSubType = isset($workbench->config->groupPushSubType) ? intval($workbench->config->groupPushSubType) : 1; // 默认1=群群发 - $workbench->config->ownerWechatIds = json_decode($workbench->config->ownerWechatIds ?? '[]', true) ?: []; - // 根据targetType解析不同的数据 - if ($workbench->config->targetType == 1) { - // 群推送 - $workbench->config->wechatGroups = json_decode($workbench->config->groups, true) ?: []; - $workbench->config->wechatFriends = []; - $workbench->config->trafficPools = []; - // 群推送不需要devices字段 - // 群公告相关字段 - if ($workbench->config->groupPushSubType == 2) { - $workbench->config->announcementContent = isset($workbench->config->announcementContent) ? $workbench->config->announcementContent : ''; - $workbench->config->enableAiRewrite = isset($workbench->config->enableAiRewrite) ? intval($workbench->config->enableAiRewrite) : 0; - $workbench->config->aiRewritePrompt = isset($workbench->config->aiRewritePrompt) ? $workbench->config->aiRewritePrompt : ''; - } - } else { - // 好友推送 - $workbench->config->wechatFriends = json_decode($workbench->config->friends, true) ?: []; - $workbench->config->wechatGroups = []; - $workbench->config->trafficPools = json_decode($workbench->config->trafficPools ?? '[]', true) ?: []; - } - $workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true); - $workbench->config->postPushTags = json_decode($workbench->config->postPushTags ?? '[]', true) ?: []; - unset($workbench->groupPush, $workbench->group_push); - } - break; - //建群助手 - case self::TYPE_GROUP_CREATE: - if (!empty($workbench->groupCreate)) { - $workbench->config = $workbench->groupCreate; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->poolGroups = json_decode($workbench->config->poolGroups, true); - $workbench->config->wechatGroups = json_decode($workbench->config->wechatGroups, true); - $workbench->config->admins = json_decode($workbench->config->admins ?? '[]', true) ?: []; - - // 处理群管理员相关字段 - $workbench->config->groupAdminEnabled = !empty($workbench->config->admins) ? 1 : 0; - - // 如果有管理员,设置groupAdminWechatId为第一个管理员的ID(用于前端回显) - $workbench->config->groupAdminWechatId = !empty($workbench->config->admins) ? $workbench->config->admins[0] : null; - - // 统计已建群数(状态为成功且groupId不为空的记录,按groupId分组去重) - $createdGroupsCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbench->id) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->where('groupId', '<>', null) - ->group('groupId') - ->count(); - - // 统计总人数(该工作台的所有记录数) - $totalMembersCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbench->id) - ->count(); - - // 添加统计信息 - $workbench->config->stats = [ - 'createdGroupsCount' => $createdGroupsCount, - 'totalMembersCount' => $totalMembersCount - ]; - - unset($workbench->groupCreate, $workbench->group_create); - } - break; - //流量分发 - case self::TYPE_TRAFFIC_DISTRIBUTION: - if (!empty($workbench->trafficConfig)) { - $workbench->config = $workbench->trafficConfig; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->accountGroups = json_decode($workbench->config->account, true); - $workbench->config->poolGroups = json_decode($workbench->config->pools, true); - $config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->find(); - $workbench->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--'; - - //统计 - $labels = $workbench->config->poolGroups; - $totalUsers = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where([ - ['wf.isDeleted', '=', 0], - ['sa.departmentId', '=', $workbench->companyId] - ]) - ->whereIn('wa.currentDeviceId', $workbench->config->deviceGroups) - ->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId') - ->where(function ($q) use ($labels) { - foreach ($labels as $label) { - $q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')"); - } - })->count(); - - $totalAccounts = Db::table('s2_company_account') - ->alias('a') - ->where(['a.departmentId' => $workbench->companyId, 'a.status' => 0]) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%') - ->group('a.id') - ->count(); - - $dailyAverage = Db::name('workbench_traffic_config_item') - ->where('workbenchId', $workbench->id) - ->count(); - $day = (time() - strtotime($workbench->createTime)) / 86400; - $day = intval($day); - - - if ($dailyAverage > 0) { - $dailyAverage = $dailyAverage / $totalAccounts / $day; - } - - $workbench->config->total = [ - 'dailyAverage' => intval($dailyAverage), - 'totalAccounts' => $totalAccounts, - 'deviceCount' => count($workbench->config->deviceGroups), - 'poolCount' => count($workbench->config->poolGroups), - 'totalUsers' => $totalUsers >> 0 - ]; - unset($workbench->trafficConfig, $workbench->traffic_config); - } - break; - case self::TYPE_IMPORT_CONTACT: - if (!empty($workbench->importContact)) { - $workbench->config = $workbench->importContact; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->poolGroups = json_decode($workbench->config->pools, true); - } - unset($workbench->importContact, $workbench->import_contact); - break; - } - unset( - $workbench->autoLike, - $workbench->momentsSync, - $workbench->groupPush, - $workbench->groupCreate, - $workbench->config->devices, - $workbench->config->friends, - $workbench->config->groups, - $workbench->config->contentLibraries, - $workbench->config->account, - ); - - - //获取设备信息 - if (!empty($workbench->config->deviceGroups)) { - $deviceList = DeviceModel::alias('d') - ->field([ - 'd.id', 'd.imei', 'd.memo', 'd.alive', - 'l.wechatId', - 'a.nickname', 'a.alias', 'a.avatar', 'a.alias', '0 totalFriend' - ]) - ->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId') - ->leftJoin('wechat_account a', 'l.wechatId = a.wechatId') - ->whereIn('d.id', $workbench->config->deviceGroups) - ->order('d.id desc') - ->select(); - - foreach ($deviceList as &$device) { - $curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find(); - $device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0; - } - unset($device); - - $workbench->config->deviceGroupsOptions = $deviceList; - } else { - $workbench->config->deviceGroupsOptions = []; - } - - - // 获取群(当targetType=1时) - if (!empty($workbench->config->wechatGroups) && isset($workbench->config->targetType) && $workbench->config->targetType == 1) { - $groupList = Db::name('wechat_group')->alias('wg') - ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') - ->where('wg.id', 'in', $workbench->config->wechatGroups) - ->order('wg.id', 'desc') - ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wa.avatar,wa.alias,wg.avatar as groupAvatar') - ->select(); - $workbench->config->wechatGroupsOptions = $groupList; - } else { - $workbench->config->wechatGroupsOptions = []; - } - - // 获取好友(当targetType=2时) - if (!empty($workbench->config->wechatFriends) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { - $friendList = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where('wf.id', 'in', $workbench->config->wechatFriends) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar') - ->select(); - $workbench->config->wechatFriendsOptions = $friendList; - } else { - $workbench->config->wechatFriendsOptions = []; - } - - // 获取流量池(当targetType=2时) - if (!empty($workbench->config->trafficPools) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { - $poolList = Db::name('traffic_source_package')->alias('tsp') - ->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0') - ->whereIn('tsp.id', $workbench->config->trafficPools) - ->where('tsp.isDel', 0) - ->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0]) - ->field('tsp.id,tsp.name,tsp.description,tsp.pic,COUNT(tspi.id) as itemCount') - ->group('tsp.id') - ->order('tsp.id', 'desc') - ->select(); - $workbench->config->trafficPoolsOptions = $poolList; - } else { - $workbench->config->trafficPoolsOptions = []; - } - - // 获取内容库名称 - if (!empty($workbench->config->contentGroups)) { - $libraryNames = ContentLibrary::where('id', 'in', $workbench->config->contentGroups)->select(); - $workbench->config->contentGroupsOptions = $libraryNames; - } else { - $workbench->config->contentGroupsOptions = []; - } - - //账号 - if (!empty($workbench->config->accountGroups)) { - $account = Db::table('s2_company_account')->alias('a') - ->where(['a.departmentId' => $this->request->userInfo['companyId'], 'a.status' => 0]) - ->whereIn('a.id', $workbench->config->accountGroups) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%') - ->field('a.id,a.userName,a.realName,a.nickname,a.memo') - ->select(); - $workbench->config->accountGroupsOptions = $account; - } else { - $workbench->config->accountGroupsOptions = []; - } - - if (!empty($workbench->config->poolGroups)) { - $poolGroupsOptions = Db::name('traffic_source_package')->alias('tsp') - ->join('traffic_source_package_item tspi', 'tspi.packageId=tsp.id', 'left') - ->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0]) - ->whereIn('tsp.id', $workbench->config->poolGroups) - ->field('tsp.id,tsp.name,tsp.description,tsp.createTime,count(tspi.id) as num') - ->group('tsp.id') - ->select(); - $workbench->config->poolGroupsOptions = $poolGroupsOptions; - } else { - $workbench->config->poolGroupsOptions = []; - } - - if (!empty($workbench->config->ownerWechatIds)) { - $ownerWechatOptions = Db::name('wechat_account') - ->whereIn('id', $workbench->config->ownerWechatIds) - ->field('id,wechatId,nickName,avatar,alias') - ->select(); - $workbench->config->ownerWechatOptions = $ownerWechatOptions; - } else { - $workbench->config->ownerWechatOptions = []; - } - - // 获取群组选项(自动建群) - if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->wechatGroups)) { - // 分离数字ID(好友ID)和字符串ID(手动创建的群组) - $friendIds = []; - $manualGroupIds = []; - - foreach ($workbench->config->wechatGroups as $groupId) { - if (is_numeric($groupId)) { - $friendIds[] = intval($groupId); - } else { - $manualGroupIds[] = $groupId; - } - } - - $wechatGroupsOptions = []; - - // 查询好友信息(数字ID) - if (!empty($friendIds)) { - $friendList = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->join(['s2_company_account' => 'ca'], 'ca.id = wf.accountId', 'left') - ->where('wf.id', 'in', $friendIds) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname,wf.avatar,wf.alias,wf.gender,wf.phone,wa.nickName as accountNickname,ca.userName as account,ca.realName as username,wf.createTime,wf.updateTime,wf.deleteTime,wf.ownerWechatId') - ->select(); - - // 获取群主信息 - foreach ($friendList as &$friend) { - if (!empty($friend['ownerWechatId'])) { - $owner = Db::name('wechat_account') - ->where('wechatId', $friend['ownerWechatId']) - ->field('nickName,alias') - ->find(); - $friend['ownerNickname'] = $owner['nickName'] ?? ''; - $friend['ownerAlias'] = $owner['alias'] ?? ''; - } else { - $friend['ownerNickname'] = ''; - $friend['ownerAlias'] = ''; - } - $friend['isManual'] = ''; - // 格式化时间 - $friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : ''; - $friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : ''; - $friend['deleteTime'] = !empty($friend['deleteTime']) ? date('Y-m-d H:i:s', $friend['deleteTime']) : ''; - } - unset($friend); - - $wechatGroupsOptions = array_merge($wechatGroupsOptions, $friendList); - } - - // 处理手动创建的群组(字符串ID) - if (!empty($manualGroupIds)) { - foreach ($manualGroupIds as $groupId) { - // 手动创建的群组,只返回基本信息 - $wechatGroupsOptions[] = [ - 'id' => $groupId, - 'wechatId' => $groupId, - 'nickname' => $groupId, - 'avatar' => '', - 'isManual' => 1 - ]; - } - } - - $workbench->config->wechatGroupsOptions = $wechatGroupsOptions; - } else { - $workbench->config->wechatGroupsOptions = []; - } - - // 获取管理员选项(自动建群) - if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->admins)) { - $adminOptions = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where('wf.id', 'in', $workbench->config->admins) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar') - ->select(); - $workbench->config->adminsOptions = $adminOptions; - } else { - $workbench->config->adminsOptions = []; - } - - return json(['code' => 200, 'msg' => '获取成功', 'data' => $workbench]); - } - - /** - * 更新工作台 - * @return \think\response\Json - */ - public function update() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - // 获取请求参数 - $param = $this->request->post(); - - // 验证数据 - $validate = new WorkbenchValidate; - if (!$validate->scene('update')->check($param)) { - return json(['code' => 400, 'msg' => $validate->getError()]); - } - - - $where = [ - ['id', '=', $param['id']], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - // 查询工作台是否存在 - $workbench = Workbench::where($where)->find(); - if (!$workbench) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - Db::startTrans(); - try { - // 更新工作台基本信息 - $workbench->name = $param['name']; - $workbench->status = !empty($param['status']) ? 1 : 0; - $workbench->autoStart = !empty($param['autoStart']) ? 1 : 0; - $workbench->updateTime = time(); - $workbench->save(); - - // 根据类型更新对应的配置 - switch ($workbench->type) { - case self::TYPE_AUTO_LIKE: - $config = WorkbenchAutoLike::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->interval = $param['interval']; - $config->maxLikes = $param['maxLikes']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->contentTypes = json_encode($param['contentTypes']); - $config->devices = json_encode($param['deviceGroups']); - $config->friends = json_encode($param['wechatFriends']); - // $config->targetGroups = json_encode($param['targetGroups']); - // $config->tagOperator = $param['tagOperator']; - $config->friendMaxLikes = $param['friendMaxLikes']; - $config->friendTags = $param['friendTags']; - $config->enableFriendTags = $param['enableFriendTags']; - $config->updateTime = time(); - $config->save(); - } - break; - - case self::TYPE_MOMENTS_SYNC: - $config = WorkbenchMomentsSync::where('workbenchId', $param['id'])->find(); - if ($config) { - if (!empty($param['contentGroups'])) { - foreach ($param['contentGroups'] as $library) { - if (isset($library['id']) && !empty($library['id'])) { - $contentLibraries[] = $library['id']; - } else { - $contentLibraries[] = $library; - } - } - } else { - $contentLibraries = []; - } - - $config->syncInterval = $param['syncInterval']; - $config->syncCount = $param['syncCount']; - $config->syncType = $param['syncType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->accountType = $param['accountType']; - $config->devices = json_encode($param['deviceGroups']); - $config->contentLibraries = json_encode($contentLibraries); - $config->updateTime = time(); - $config->save(); - } - break; - - case self::TYPE_GROUP_PUSH: - $config = WorkbenchGroupPush::where('workbenchId', $param['id'])->find(); - if ($config) { - $ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? null, $config); - $groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds, $config); - $groupPushData['updateTime'] = time(); - $config->save($groupPushData); - } - break; - - case self::TYPE_GROUP_CREATE: - $config = WorkbenchGroupCreate::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->startTime = $param['startTime'] ?? ''; - $config->endTime = $param['endTime'] ?? ''; - $config->groupSizeMin = intval($param['groupSizeMin'] ?? 3); - $config->groupSizeMax = intval($param['groupSizeMax'] ?? 38); - $config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20); - $config->groupNameTemplate = $param['groupNameTemplate'] ?? ''; - $config->groupDescription = $param['groupDescription'] ?? ''; - $config->poolGroups = json_encode($param['poolGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->wechatGroups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE); - - // 处理群管理员:如果启用了群管理员且有指定管理员,则保存到admins字段 - $admins = []; - if (!empty($param['groupAdminEnabled']) && !empty($param['groupAdminWechatId'])) { - // 如果groupAdminWechatId是数组,取第一个;如果是单个值,直接使用 - $adminWechatId = is_array($param['groupAdminWechatId']) ? $param['groupAdminWechatId'][0] : $param['groupAdminWechatId']; - // 如果是好友ID,直接添加到admins;如果是wechatId,需要转换为好友ID - if (is_numeric($adminWechatId)) { - $admins[] = intval($adminWechatId); - } else { - // 如果是wechatId字符串,需要查询对应的好友ID - $friend = Db::table('s2_wechat_friend')->where('wechatId', $adminWechatId)->find(); - if ($friend) { - $admins[] = intval($friend['id']); - } - } - } - // 如果传入了admins参数,优先使用(兼容旧逻辑) - if (!empty($param['admins']) && is_array($param['admins'])) { - $admins = array_merge($admins, $param['admins']); - } - $config->admins = json_encode(array_unique($admins), JSON_UNESCAPED_UNICODE); - - $config->updateTime = time(); - $config->save(); - } - break; - case self::TYPE_TRAFFIC_DISTRIBUTION: - $config = WorkbenchTrafficConfig::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->distributeType = $param['distributeType']; - $config->maxPerDay = $param['maxPerDay']; - $config->timeType = $param['timeType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->devices = json_encode($param['deviceGroups']); - $config->pools = json_encode($param['poolGroups']); - $config->account = json_encode($param['accountGroups']); - $config->updateTime = time(); - $config->save(); - } - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = WorkbenchImportContact::where('workbenchId', $param['id'])->find();; - if ($config) { - $config->devices = json_encode($param['deviceGroups']); - $config->pools = json_encode($param['poolGroups']); - $config->num = $param['num']; - $config->clearContact = $param['clearContact']; - $config->remark = $param['remark']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->save(); - } - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '更新成功']); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]); - } - } - - /** - * 更新工作台状态 - * @return \think\response\Json - */ - public function updateStatus() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - $id = $this->request->param('id', ''); - - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - $workbench->status = !$workbench['status']; - $workbench->save(); - - return json(['code' => 200, 'msg' => '更新成功']); - } - - /** - * 删除工作台(软删除) - */ - public function delete() - { - $id = $this->request->param('id'); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - $workbench = Workbench::where($where)->find(); - - if (!$workbench) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - // 软删除 - $workbench->isDel = 1; - $workbench->deleteTime = time(); - $workbench->save(); - - return json(['code' => 200, 'msg' => '删除成功']); - } - - /** - * 拷贝工作台 - * @return \think\response\Json - */ - public function copy() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - $id = $this->request->post('id'); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 验证权限并获取原数据 - $workbench = Workbench::where([ - ['id', '=', $id], - ['userId', '=', $this->request->userInfo['id']] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - Db::startTrans(); - try { - // 创建新的工作台基本信息 - $newWorkbench = new Workbench; - $newWorkbench->name = $workbench->name . ' copy'; - $newWorkbench->type = $workbench->type; - $newWorkbench->status = 1; // 新拷贝的默认启用 - $newWorkbench->autoStart = $workbench->autoStart; - $newWorkbench->userId = $this->request->userInfo['id']; - $newWorkbench->companyId = $this->request->userInfo['companyId']; - $newWorkbench->save(); - - // 根据类型拷贝对应的配置 - switch ($workbench->type) { - case self::TYPE_AUTO_LIKE: - $config = WorkbenchAutoLike::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchAutoLike; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->interval = $config->interval; - $newConfig->maxLikes = $config->maxLikes; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->contentTypes = $config->contentTypes; - $newConfig->devices = $config->devices; - $newConfig->friends = $config->friends; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_MOMENTS_SYNC: - $config = WorkbenchMomentsSync::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchMomentsSync; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->syncInterval = $config->syncInterval; - $newConfig->syncCount = $config->syncCount; - $newConfig->syncType = $config->syncType; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->accountType = $config->accountType; - $newConfig->devices = $config->devices; - $newConfig->contentLibraries = $config->contentLibraries; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_GROUP_PUSH: - $config = WorkbenchGroupPush::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchGroupPush; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->pushType = $config->pushType; - $newConfig->targetType = isset($config->targetType) ? $config->targetType : 1; // 默认1=群推送 - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->maxPerDay = $config->maxPerDay; - $newConfig->pushOrder = $config->pushOrder; - $newConfig->isLoop = $config->isLoop; - $newConfig->status = $config->status; - $newConfig->groups = $config->groups; - $newConfig->friends = $config->friends; - $newConfig->contentLibraries = $config->contentLibraries; - $newConfig->trafficPools = property_exists($config, 'trafficPools') ? $config->trafficPools : json_encode([], JSON_UNESCAPED_UNICODE); - $newConfig->socialMediaId = $config->socialMediaId; - $newConfig->promotionSiteId = $config->promotionSiteId; - $newConfig->ownerWechatIds = $config->ownerWechatIds; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_GROUP_CREATE: - $config = WorkbenchGroupCreate::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchGroupCreate; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->devices = $config->devices; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->groupSizeMin = $config->groupSizeMin; - $newConfig->groupSizeMax = $config->groupSizeMax; - $newConfig->maxGroupsPerDay = $config->maxGroupsPerDay; - $newConfig->groupNameTemplate = $config->groupNameTemplate; - $newConfig->groupDescription = $config->groupDescription; - $newConfig->poolGroups = $config->poolGroups; - $newConfig->wechatGroups = $config->wechatGroups; - $newConfig->admins = $config->admins ?? json_encode([], JSON_UNESCAPED_UNICODE); - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = WorkbenchImportContact::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchImportContact; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->devices = $config->devices; - $newConfig->pools = $config->pools; - $newConfig->num = $config->num; - $newConfig->clearContact = $config->clearContact; - $newConfig->remark = $config->remark; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->createTime = time(); - $newConfig->save(); - } - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '拷贝成功', 'data' => ['id' => $newWorkbench->id]]); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '拷贝失败:' . $e->getMessage()]); - } - } - - /** - * 获取点赞记录列表 - * @return \think\response\Json - */ - public function getLikeRecords() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchAutoLikeController(); - $controller->request = $this->request; - return $controller->getLikeRecords(); - } - - /** - * 获取朋友圈发布记录列表 - * @return \think\response\Json - */ - public function getMomentsRecords() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController(); - $controller->request = $this->request; - return $controller->getMomentsRecords(); - } - - /** - * 获取朋友圈发布统计 - * @return \think\response\Json - */ - public function getMomentsStats() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController(); - $controller->request = $this->request; - return $controller->getMomentsStats(); - } - - /** - * 获取流量分发记录列表 - * @return \think\response\Json - */ - public function getTrafficDistributionRecords() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $workbenchId = $this->request->param('workbenchId', 0); - - $where = [ - ['wtdi.workbenchId', '=', $workbenchId] - ]; - - // 查询分发记录 - $list = Db::name('workbench_traffic_distribution_item')->alias('wtdi') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left') - ->field([ - 'wtdi.id', - 'wtdi.workbenchId', - 'wtdi.wechatAccountId', - 'wtdi.wechatFriendId', - 'wtdi.createTime as distributeTime', - 'wtdi.status', - 'wtdi.errorMsg', - 'wa.nickName as operatorName', - 'wa.avatar as operatorAvatar', - 'wf.nickName as friendName', - 'wf.avatar as friendAvatar', - 'wf.gender', - 'wf.province', - 'wf.city' - ]) - ->where($where) - ->order('wtdi.createTime', 'desc') - ->page($page, $limit) - ->select(); - - // 处理数据 - foreach ($list as &$item) { - // 处理时间格式 - $item['distributeTime'] = date('Y-m-d H:i:s', $item['distributeTime']); - - // 处理性别 - $genderMap = [ - 0 => '未知', - 1 => '男', - 2 => '女' - ]; - $item['genderText'] = $genderMap[$item['gender']] ?? '未知'; - - // 处理状态文字 - $statusMap = [ - 0 => '待分发', - 1 => '分发成功', - 2 => '分发失败' - ]; - $item['statusText'] = $statusMap[$item['status']] ?? '未知状态'; - } - - // 获取总记录数 - $total = Db::name('workbench_traffic_distribution_item')->alias('wtdi') - ->where($where) - ->count(); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取流量分发统计 - * @return \think\response\Json - */ - public function getTrafficDistributionStats() - { - $workbenchId = $this->request->param('workbenchId', 0); - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 获取今日数据 - $todayStart = strtotime(date('Y-m-d') . ' 00:00:00'); - $todayEnd = strtotime(date('Y-m-d') . ' 23:59:59'); - - $todayStats = Db::name('workbench_traffic_distribution_item') - ->where([ - ['workbenchId', '=', $workbenchId], - ['createTime', 'between', [$todayStart, $todayEnd]] - ]) - ->field([ - 'COUNT(*) as total', - 'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success', - 'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed' - ]) - ->find(); - - // 获取总数据 - $totalStats = Db::name('workbench_traffic_distribution_item') - ->where('workbenchId', $workbenchId) - ->field([ - 'COUNT(*) as total', - 'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success', - 'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed' - ]) - ->find(); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'today' => [ - 'total' => intval($todayStats['total']), - 'success' => intval($todayStats['success']), - 'failed' => intval($todayStats['failed']) - ], - 'total' => [ - 'total' => intval($totalStats['total']), - 'success' => intval($totalStats['success']), - 'failed' => intval($totalStats['failed']) - ] - ] - ]); - } - - /** - * 获取流量分发详情 - * @return \think\response\Json - */ - public function getTrafficDistributionDetail() - { - $id = $this->request->param('id', 0); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - $detail = Db::name('workbench_traffic_distribution_item')->alias('wtdi') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left') - ->field([ - 'wtdi.id', - 'wtdi.workbenchId', - 'wtdi.wechatAccountId', - 'wtdi.wechatFriendId', - 'wtdi.createTime as distributeTime', - 'wtdi.status', - 'wtdi.errorMsg', - 'wa.nickName as operatorName', - 'wa.avatar as operatorAvatar', - 'wf.nickName as friendName', - 'wf.avatar as friendAvatar', - 'wf.gender', - 'wf.province', - 'wf.city', - 'wf.signature', - 'wf.remark' - ]) - ->where('wtdi.id', $id) - ->find(); - - if (empty($detail)) { - return json(['code' => 404, 'msg' => '记录不存在']); - } - - // 处理数据 - $detail['distributeTime'] = date('Y-m-d H:i:s', $detail['distributeTime']); - - // 处理性别 - $genderMap = [ - 0 => '未知', - 1 => '男', - 2 => '女' - ]; - $detail['genderText'] = $genderMap[$detail['gender']] ?? '未知'; - - // 处理状态文字 - $statusMap = [ - 0 => '待分发', - 1 => '分发成功', - 2 => '分发失败' - ]; - $detail['statusText'] = $statusMap[$detail['status']] ?? '未知状态'; - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => $detail - ]); - } - - /** - * 创建流量分发计划 - * @return \think\response\Json - */ - public function createTrafficPlan() - { - $param = $this->request->post(); - Db::startTrans(); - try { - // 1. 创建主表 - $planId = Db::name('ck_workbench')->insertGetId([ - 'name' => $param['name'], - 'type' => self::TYPE_TRAFFIC_DISTRIBUTION, - 'status' => 1, - 'autoStart' => $param['autoStart'] ?? 0, - 'userId' => $this->request->userInfo['id'], - 'companyId' => $this->request->userInfo['companyId'], - 'createTime' => time(), - 'updateTime' => time() - ]); - // 2. 创建扩展表 - Db::name('ck_workbench_traffic_config')->insert([ - 'workbenchId' => $planId, - 'distributeType' => $param['distributeType'], - 'maxPerDay' => $param['maxPerDay'], - 'timeType' => $param['timeType'], - 'startTime' => $param['startTime'], - 'endTime' => $param['endTime'], - 'targets' => json_encode($param['targets'], JSON_UNESCAPED_UNICODE), - 'pools' => json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE), - 'createTime' => time(), - 'updateTime' => time() - ]); - Db::commit(); - return json(['code' => 200, 'msg' => '创建成功']); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]); - } - } - - /** - * 获取所有微信好友标签及数量统计 - * @return \think\response\Json - */ - public function getDeviceLabels() - { - $deviceIds = $this->request->param('deviceIds', ''); - $companyId = $this->request->userInfo['companyId']; - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - - $where = [ - ['wc.companyId', '=', $companyId], - ]; - - if (!empty($deviceIds)) { - $deviceIds = explode(',', $deviceIds); - $where[] = ['dwl.deviceId', 'in', $deviceIds]; - } - - $wechatAccounts = Db::name('wechat_customer')->alias('wc') - ->join('device_wechat_login dwl', 'dwl.wechatId = wc.wechatId AND dwl.companyId = wc.companyId AND dwl.alive = 1') - ->join(['s2_wechat_account' => 'wa'], 'wa.wechatId = wc.wechatId') - ->where($where) - ->field('wa.id,wa.wechatId,wa.nickName,wa.labels') - ->select(); - $labels = []; - $wechatIds = []; - foreach ($wechatAccounts as $account) { - $labelArr = json_decode($account['labels'], true); - if (is_array($labelArr)) { - foreach ($labelArr as $label) { - if ($label !== '' && $label !== null) { - $labels[] = $label; - } - } - } - $wechatIds[] = $account['wechatId']; - } - // 去重(只保留一个) - $labels = array_values(array_unique($labels)); - $wechatIds = array_unique($wechatIds); - - // 搜索过滤 - if (!empty($keyword)) { - $labels = array_filter($labels, function ($label) use ($keyword) { - return mb_stripos($label, $keyword) !== false; - }); - $labels = array_values($labels); // 重新索引数组 - } - - - // 分页处理 - $labels2 = array_slice($labels, ($page - 1) * $limit, $limit); - - // 统计数量 - $newLabel = []; - foreach ($labels2 as $label) { - $friendCount = Db::table('s2_wechat_friend') - ->whereIn('ownerWechatId', $wechatIds) - ->where('labels', 'like', '%"' . $label . '"%') - ->count(); - $newLabel[] = [ - 'label' => $label, - 'count' => $friendCount - ]; - } - - // 返回结果 - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $newLabel, - 'total' => count($labels), - ] - ]); - } - - - /** - * 获取群列表 - * @return \think\response\Json - */ - public function getGroupList() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - - $where = [ - ['wg.deleteTime', '=', 0], - ['wg.companyId', '=', $this->request->userInfo['companyId']], - ]; - - if (!empty($keyword)) { - $where[] = ['wg.name', 'like', '%' . $keyword . '%']; - } - - $query = Db::name('wechat_group')->alias('wg') - ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') - ->where($where); - - $total = $query->count(); - $list = $query->order('wg.id', 'desc') - ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wg.createTime,wa.avatar,wa.alias,wg.avatar as groupAvatar') - ->page($page, $limit) - ->select(); - - // 优化:格式化时间,头像兜底 - $defaultGroupAvatar = ''; - $defaultAvatar = ''; - foreach ($list as &$item) { - $item['createTime'] = $item['createTime'] ? date('Y-m-d H:i:s', $item['createTime']) : ''; - $item['groupAvatar'] = $item['groupAvatar'] ?: $defaultGroupAvatar; - $item['avatar'] = $item['avatar'] ?: $defaultAvatar; - } - - return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]); - } - - /** - * 获取流量池列表 - * @return \think\response\Json - */ - public function getTrafficPoolList() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - $companyId = $this->request->userInfo['companyId']; - - $baseQuery = Db::name('traffic_source_package')->alias('tsp') - ->where('tsp.isDel', 0) - ->whereIn('tsp.companyId', [$companyId, 0]); - - if (!empty($keyword)) { - $baseQuery->whereLike('tsp.name', '%' . $keyword . '%'); - } - - $total = (clone $baseQuery)->count(); - - $list = $baseQuery - ->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0') - ->field('tsp.id,tsp.name,tsp.description,tsp.pic,tsp.companyId,COUNT(tspi.id) as itemCount,max(tspi.createTime) as latestImportTime') - ->group('tsp.id') - ->order('tsp.id', 'desc') - ->page($page, $limit) - ->select(); - - foreach ($list as &$item) { - $item['latestImportTime'] = !empty($item['latestImportTime']) ? date('Y-m-d H:i:s', $item['latestImportTime']) : ''; - } - unset($item); - - return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]); - } - - - public function getAccountList() - { - $companyId = $this->request->userInfo['companyId']; - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $query = Db::table('s2_company_account') - ->alias('a') - ->where(['a.departmentId' => $companyId, 'a.status' => 0]) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%'); - - $total = $query->count(); - $list = $query->field('a.id,a.userName,a.realName,a.nickname,a.memo') - ->page($page, $limit) - ->select(); - - - return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]); - } - - - /** - * 获取京东联盟导购媒体 - * @return \think\response\Json - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\ModelNotFoundException - * @throws \think\exception\DbException - */ - public function getJdSocialMedia() - { - $data = Db::name('jd_social_media')->order('id DESC')->select(); - return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]); - } - - /** - * 获取京东联盟广告位 - * @return \think\response\Json - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\ModelNotFoundException - * @throws \think\exception\DbException - */ - public function getJdPromotionSite() - { - $id = $this->request->param('id', ''); - if (empty($id)) { - return json(['code' => 500, 'msg' => '参数缺失']); - } - - $data = Db::name('jd_promotion_site')->where('jdSocialMediaId', $id)->order('id DESC')->select(); - return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]); - } - - - //京东转链-京推推 - public function changeLink($content = '', $positionid = '') - { - $unionId = Env::get('jd.unionId', ''); - $jttAppId = Env::get('jd.jttAppId', ''); - $appKey = Env::get('jd.appKey', ''); - $apiUrl = Env::get('jd.apiUrl', ''); - - $content = !empty($content) ? $content : $this->request->param('content', ''); - $positionid = !empty($positionid) ? $positionid : $this->request->param('positionid', ''); - - - if (empty($content)) { - return json_encode(['code' => 500, 'msg' => '转链的内容为空']); - } - - // 验证是否包含链接 - if (!$this->containsLink($content)) { - return json_encode(['code' => 500, 'msg' => '内容中未检测到有效链接']); - } - - if (empty($unionId) || empty($jttAppId) || empty($appKey) || empty($apiUrl)) { - return json_encode(['code' => 500, 'msg' => '参数缺失']); - } - $params = [ - 'unionid' => $unionId, - 'content' => $content, - 'appid' => $jttAppId, - 'appkey' => $appKey, - 'v' => 'v2' - ]; - - if (!empty($positionid)) { - $params['positionid'] = $positionid; - } - - $res = requestCurl($apiUrl, $params, 'GET', [], 'json'); - $res = json_decode($res, true); - if (empty($res)) { - return json_encode(['code' => 500, 'msg' => '未知错误']); - } - $result = $res['result']; - if ($res['return'] == 0) { - return json_encode(['code' => 200, 'data' => $result['chain_content'], 'msg' => $result['msg']]); - } else { - return json_encode(['code' => 500, 'msg' => $result['msg']]); - } - } - - - public function getTrafficList() - { - $companyId = $this->request->userInfo['companyId']; - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - $workbenchId = $this->request->param('workbenchId', ''); - $isRecycle = $this->request->param('isRecycle', ''); - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - $workbench = Db::name('workbench')->where(['id' => $workbenchId, 'isDel' => 0, 'companyId' => $companyId, 'type' => 5])->find(); - - if (empty($workbench)) { - return json(['code' => 400, 'msg' => '该任务不存在或已删除']); - } - $query = Db::name('workbench_traffic_config_item')->alias('wtc') - ->join(['s2_wechat_friend' => 'wf'], 'wtc.wechatFriendId = wf.id') - ->join('users u', 'wtc.wechatAccountId = u.s2_accountId', 'left') - ->field([ - 'wtc.id', 'wtc.isRecycle', 'wtc.isRecycle', 'wtc.createTime','wtc.recycleTime', - 'wf.wechatId', 'wf.alias', 'wf.nickname', 'wf.avatar', 'wf.gender', 'wf.phone', - 'u.account', 'u.username' - ]) - ->where(['wtc.workbenchId' => $workbenchId]) - ->order('wtc.id DESC'); - - if (!empty($keyword)) { - $query->where('wf.wechatId|wf.alias|wf.nickname|wf.phone|u.account|u.username', 'like', '%' . $keyword . '%'); - } - - if ($isRecycle != '' || $isRecycle != null) { - $query->where('isRecycle',$isRecycle); - } - - - - $total = $query->count(); - $list = $query->page($page, $limit)->select(); - - foreach ($list as &$item) { - $item['createTime'] = date('Y-m-d H:i:s', $item['createTime']); - $item['recycleTime'] = date('Y-m-d H:i:s', $item['recycleTime']); - } - unset($item); - - - $data = [ - 'total' => $total, - 'list' => $list, - ]; - - return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]); - - } - - /** - * 规范化客服微信ID列表 - * @param mixed $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @return array - * @throws \Exception - */ - private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array - { - if ($ownerWechatIds === null) { - $existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : []; - if (empty($existing)) { - throw new \Exception('请至少选择一个客服微信'); - } - return $existing; - } - - if (!is_array($ownerWechatIds)) { - throw new \Exception('客服参数格式错误'); - } - - $normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误'); - if (empty($normalized)) { - throw new \Exception('请至少选择一个客服微信'); - } - return $normalized; - } - - /** - * 构建群推送配置数据 - * @param array $param - * @param array $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @return array - * @throws \Exception - */ - private function prepareGroupPushData(array $param, array $ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array - { - $targetTypeDefault = $originalConfig ? intval($originalConfig->targetType) : 1; - $targetType = intval($this->getParamValue($param, 'targetType', $targetTypeDefault)) ?: 1; - - $groupPushSubTypeDefault = $originalConfig ? intval($originalConfig->groupPushSubType) : 1; - $groupPushSubType = intval($this->getParamValue($param, 'groupPushSubType', $groupPushSubTypeDefault)) ?: 1; - if (!in_array($groupPushSubType, [1, 2], true)) { - $groupPushSubType = 1; - } - - $data = [ - 'pushType' => $this->toBoolInt($this->getParamValue($param, 'pushType', $originalConfig->pushType ?? 0)), - 'targetType' => $targetType, - 'startTime' => $this->getParamValue($param, 'startTime', $originalConfig->startTime ?? ''), - 'endTime' => $this->getParamValue($param, 'endTime', $originalConfig->endTime ?? ''), - 'maxPerDay' => intval($this->getParamValue($param, 'maxPerDay', $originalConfig->maxPerDay ?? 0)), - 'pushOrder' => $this->getParamValue($param, 'pushOrder', $originalConfig->pushOrder ?? 1), - 'groupPushSubType' => $groupPushSubType, - 'status' => $this->toBoolInt($this->getParamValue($param, 'status', $originalConfig->status ?? 0)), - 'socialMediaId' => $this->getParamValue($param, 'socialMediaId', $originalConfig->socialMediaId ?? ''), - 'promotionSiteId' => $this->getParamValue($param, 'promotionSiteId', $originalConfig->promotionSiteId ?? ''), - 'friendIntervalMin' => intval($this->getParamValue($param, 'friendIntervalMin', $originalConfig->friendIntervalMin ?? 10)), - 'friendIntervalMax' => intval($this->getParamValue($param, 'friendIntervalMax', $originalConfig->friendIntervalMax ?? 20)), - 'messageIntervalMin' => intval($this->getParamValue($param, 'messageIntervalMin', $originalConfig->messageIntervalMin ?? 1)), - 'messageIntervalMax' => intval($this->getParamValue($param, 'messageIntervalMax', $originalConfig->messageIntervalMax ?? 12)), - 'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)), - 'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE), - ]; - - if ($data['friendIntervalMin'] > $data['friendIntervalMax']) { - throw new \Exception('目标间最小间隔不能大于最大间隔'); - } - if ($data['messageIntervalMin'] > $data['messageIntervalMax']) { - throw new \Exception('消息间最小间隔不能大于最大间隔'); - } - - $contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : []; - $contentGroupsParam = $this->getParamValue($param, 'contentGroups', null); - $contentGroups = $contentGroupsParam !== null - ? $this->extractIdList($contentGroupsParam, '内容库参数格式错误') - : $contentGroupsExisting; - $data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE); - - $postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : []; - $postPushTagsParam = $this->getParamValue($param, 'postPushTags', null); - $postPushTags = $postPushTagsParam !== null - ? $this->extractIdList($postPushTagsParam, '推送标签参数格式错误') - : $postPushTagsExisting; - $data['postPushTags'] = json_encode($postPushTags, JSON_UNESCAPED_UNICODE); - - if ($targetType === 1) { - $data['isLoop'] = $this->toBoolInt($this->getParamValue($param, 'isLoop', $originalConfig->isLoop ?? 0)); - - $groupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->groups ?? []) : []; - $wechatGroups = array_key_exists('wechatGroups', $param) - ? $this->extractIdList($param['wechatGroups'], '群参数格式错误') - : $groupsExisting; - if (empty($wechatGroups)) { - throw new \Exception('群推送必须选择微信群'); - } - $data['groups'] = json_encode($wechatGroups, JSON_UNESCAPED_UNICODE); - $data['friends'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode([], JSON_UNESCAPED_UNICODE); - - if ($groupPushSubType === 2) { - $announcementContent = $this->getParamValue($param, 'announcementContent', $originalConfig->announcementContent ?? ''); - if (empty($announcementContent)) { - throw new \Exception('群公告必须输入公告内容'); - } - $enableAiRewrite = $this->toBoolInt($this->getParamValue($param, 'enableAiRewrite', $originalConfig->enableAiRewrite ?? 0)); - $aiRewritePrompt = trim((string)$this->getParamValue($param, 'aiRewritePrompt', $originalConfig->aiRewritePrompt ?? '')); - if ($enableAiRewrite === 1 && $aiRewritePrompt === '') { - throw new \Exception('启用AI智能话术改写时,必须输入改写提示词'); - } - $data['announcementContent'] = $announcementContent; - $data['enableAiRewrite'] = $enableAiRewrite; - $data['aiRewritePrompt'] = $aiRewritePrompt; - } else { - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - } else { - $data['isLoop'] = 0; - $friendsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->friends ?? []) : []; - $trafficPoolsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->trafficPools ?? []) : []; - - $friendTargets = array_key_exists('wechatFriends', $param) - ? $this->extractIdList($param['wechatFriends'], '好友参数格式错误') - : $friendsExisting; - $trafficPools = array_key_exists('trafficPools', $param) - ? $this->extractIdList($param['trafficPools'], '流量池参数格式错误') - : $trafficPoolsExisting; - - if (empty($friendTargets) && empty($trafficPools)) { - throw new \Exception('好友推送需至少选择好友或流量池'); - } - - $data['friends'] = json_encode($friendTargets, JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode($trafficPools, JSON_UNESCAPED_UNICODE); - $data['groups'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - - return $data; - } - - /** - * 获取参数值,若不存在则返回默认值 - * @param array $param - * @param string $key - * @param mixed $default - * @return mixed - */ - private function getParamValue(array $param, string $key, $default) - { - return array_key_exists($key, $param) ? $param[$key] : $default; - } - - /** - * 将值转换为整型布尔 - * @param mixed $value - * @return int - */ - private function toBoolInt($value): int - { - return empty($value) ? 0 : 1; - } - - /** - * 从参数中提取ID列表 - * @param mixed $items - * @param string $errorMessage - * @return array - * @throws \Exception - */ - private function extractIdList($items, string $errorMessage = '参数格式错误'): array - { - if (!is_array($items)) { - throw new \Exception($errorMessage); - } - - $ids = []; - foreach ($items as $item) { - if (is_array($item) && isset($item['id'])) { - $item = $item['id']; - } - if ($item === '' || $item === null) { - continue; - } - $ids[] = $item; - } - - return array_values(array_unique($ids)); - } - - /** - * 解码JSON数组 - * @param mixed $value - * @return array - */ - private function decodeJsonArray($value): array - { - if (empty($value)) { - return []; - } - if (is_array($value)) { - return $value; - } - $decoded = json_decode($value, true); - return is_array($decoded) ? $decoded : []; - } - - /** - * 验证内容是否包含链接 - * @param string $content 要检测的内容 - * @return bool - */ - private function containsLink($content) - { - // 定义各种链接的正则表达式模式 - $patterns = [ - // HTTP/HTTPS链接 - '/https?:\/\/[^\s]+/i', - // 京东商品链接 - '/item\.jd\.com\/\d+/i', - // 京东短链接 - '/u\.jd\.com\/[a-zA-Z0-9]+/i', - // 淘宝商品链接 - '/item\.taobao\.com\/item\.htm\?id=\d+/i', - // 天猫商品链接 - '/detail\.tmall\.com\/item\.htm\?id=\d+/i', - // 淘宝短链接 - '/m\.tb\.cn\/[a-zA-Z0-9]+/i', - // 拼多多链接 - '/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i', - // 苏宁易购链接 - '/product\.suning\.com\/\d+\/\d+\.html/i', - // 通用域名模式(包含常见电商域名) - '/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i', - // 通用短链接模式 - '/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i' - ]; - - // 遍历所有模式进行匹配 - foreach ($patterns as $pattern) { - if (preg_match($pattern, $content)) { - return true; - } - } - - return false; - } - - - /** - * 获取通讯录导入记录列表 - * @return \think\response\Json - */ - public function 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, - ] - ]); - } - - /** - * 获取群发统计数据 - * @return \think\response\Json - */ - public function getGroupPushStats() - { - $workbenchId = $this->request->param('workbenchId', 0); - $timeRange = $this->request->param('timeRange', '7'); // 默认最近7天 - $contentLibraryIds = $this->request->param('contentLibraryIds', ''); // 话术组筛选 - $userId = $this->request->userInfo['id']; - - // 如果指定了工作台ID,则验证权限 - if (!empty($workbenchId)) { - $workbench = Workbench::where([ - ['id', '=', $workbenchId], - ['userId', '=', $userId], - ['type', '=', self::TYPE_GROUP_PUSH], - ['isDel', '=', 0] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - } - - // 计算时间范围 - $days = intval($timeRange); - $startTime = strtotime(date('Y-m-d 00:00:00', strtotime("-{$days} days"))); - $endTime = time(); - - // 构建查询条件 - $where = [ - ['wgpi.createTime', '>=', $startTime], - ['wgpi.createTime', '<=', $endTime] - ]; - - // 如果指定了工作台ID,则限制查询范围 - if (!empty($workbenchId)) { - $where[] = ['wgpi.workbenchId', '=', $workbenchId]; - } else { - // 如果没有指定工作台ID,则查询当前用户的所有群推送工作台 - $workbenchIds = Workbench::where([ - ['userId', '=', $userId], - ['type', '=', self::TYPE_GROUP_PUSH], - ['isDel', '=', 0] - ])->column('id'); - - if (empty($workbenchIds)) { - // 如果没有工作台,返回空结果 - $workbenchIds = [-1]; - } - $where[] = ['wgpi.workbenchId', 'in', $workbenchIds]; - } - - // 话术组筛选 - 先获取符合条件的内容ID列表 - $contentIds = null; - if (!empty($contentLibraryIds)) { - $libraryIds = is_array($contentLibraryIds) ? $contentLibraryIds : explode(',', $contentLibraryIds); - $libraryIds = array_filter(array_map('intval', $libraryIds)); - if (!empty($libraryIds)) { - // 查询符合条件的内容ID - $contentIds = Db::name('content_item') - ->whereIn('libraryId', $libraryIds) - ->column('id'); - if (empty($contentIds)) { - // 如果没有符合条件的内容,返回空结果 - $contentIds = [-1]; // 使用不存在的ID,确保查询结果为空 - } - } - } - - // 1. 基础统计:触达率、回复率、平均回复时间、链接点击率 - $stats = $this->calculateBasicStats($workbenchId, $where, $startTime, $endTime, $contentIds); - - // 2. 话术组对比 - $contentLibraryComparison = $this->getContentLibraryComparison($workbenchId, $where, $startTime, $endTime, $contentIds); - - // 3. 时段分析 - $timePeriodAnalysis = $this->getTimePeriodAnalysis($workbenchId, $where, $startTime, $endTime, $contentIds); - - // 4. 互动深度(可选,需要更多数据) - $interactionDepth = $this->getInteractionDepth($workbenchId, $where, $startTime, $endTime, $contentIds); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'basicStats' => $stats, - 'contentLibraryComparison' => $contentLibraryComparison, - 'timePeriodAnalysis' => $timePeriodAnalysis, - 'interactionDepth' => $interactionDepth - ] - ]); - } - - /** - * 计算基础统计数据 - */ - private function calculateBasicStats($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - // 获取工作台配置,计算计划发送数 - // 如果 workbenchId 为空,则查询所有工作台的配置 - $configQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - - if (!empty($workbenchId)) { - $configQuery->where('wgp.workbenchId', $workbenchId); - } else { - // 如果没有指定工作台ID,需要从 where 条件中获取 workbenchId 列表 - $workbenchIdCondition = null; - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - if ($condition[1] === 'in' && is_array($condition[2])) { - $workbenchIdCondition = $condition[2]; - break; - } elseif ($condition[1] === '=') { - $workbenchIdCondition = [$condition[2]]; - break; - } - } - } - if ($workbenchIdCondition) { - $configQuery->whereIn('wgp.workbenchId', $workbenchIdCondition); - } - } - - $configs = $configQuery->select(); - $targetType = 1; // 默认值 - if (!empty($configs)) { - // 如果只有一个配置,使用它的 targetType;如果有多个,默认使用1 - $targetType = intval($configs[0]->targetType ?? 1); - } - - // 计划发送数(根据配置计算) - $plannedSend = 0; - if (!empty($configs)) { - $days = ceil(($endTime - $startTime) / 86400); - foreach ($configs as $config) { - $maxPerDay = intval($config->maxPerDay ?? 0); - $configTargetType = intval($config->targetType ?? 1); - if ($configTargetType == 1) { - // 群推送:计划发送数 = 每日推送次数 * 天数 * 群数量 - $groups = $this->decodeJsonArray($config->groups ?? []); - $plannedSend += $maxPerDay * $days * count($groups); - } else { - // 好友推送:计划发送数 = 每日推送人数 * 天数 - $plannedSend += $maxPerDay * $days; - } - } - } - - // 构建查询条件 - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - // 实际成功发送数(从推送记录表统计) - $successSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->count(); - - // 触达率 = 成功发送数 / 计划发送数 - $reachRate = $plannedSend > 0 ? round(($successSend / $plannedSend) * 100, 1) : 0; - - // 获取发送记录列表,用于查询回复 - $sentItemIds = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - - // 回复统计(通过消息表查询) - $replyStats = $this->calculateReplyStats($sentItemIds, $targetType, $startTime, $endTime); - - // 链接点击统计 - $clickStats = $this->calculateClickStats($sentItemIds, $targetType, $startTime, $endTime); - - // 计算本月对比数据(简化处理,实际应该查询上个月同期数据) - $currentMonthStart = strtotime(date('Y-m-01 00:00:00')); - $lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month'))); - $lastMonthEnd = $currentMonthStart - 1; - - // 获取本月统计数据(避免递归调用) - $currentMonthWhere = [ - ['wgpi.createTime', '>=', $currentMonthStart] - ]; - // 复制 workbenchId 条件 - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - $currentMonthWhere[] = $condition; - break; - } - } - if ($contentIds !== null) { - $currentMonthWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - $currentMonthSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($currentMonthWhere) - ->count(); - - // 获取本月配置 - $currentMonthConfigQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - if (!empty($workbenchId)) { - $currentMonthConfigQuery->where('wgp.workbenchId', $workbenchId); - } else { - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') { - $currentMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]); - break; - } - } - } - $currentMonthConfigs = $currentMonthConfigQuery->select(); - $currentMonthPlanned = 0; - if (!empty($currentMonthConfigs)) { - $currentMonthDays = ceil(($endTime - $currentMonthStart) / 86400); - foreach ($currentMonthConfigs as $currentMonthConfig) { - $currentMonthMaxPerDay = intval($currentMonthConfig->maxPerDay ?? 0); - $currentMonthTargetType = intval($currentMonthConfig->targetType ?? 1); - if ($currentMonthTargetType == 1) { - $currentMonthGroups = $this->decodeJsonArray($currentMonthConfig->groups ?? []); - $currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays * count($currentMonthGroups); - } else { - $currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays; - } - } - } - $currentMonthReachRate = $currentMonthPlanned > 0 ? round(($currentMonthSend / $currentMonthPlanned) * 100, 1) : 0; - - // 获取上个月统计数据 - $lastMonthWhere = [ - ['wgpi.createTime', '>=', $lastMonthStart], - ['wgpi.createTime', '<=', $lastMonthEnd] - ]; - // 复制 workbenchId 条件 - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - $lastMonthWhere[] = $condition; - break; - } - } - if ($contentIds !== null) { - $lastMonthWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - $lastMonthSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($lastMonthWhere) - ->count(); - - // 获取上个月配置 - $lastMonthConfigQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - if (!empty($workbenchId)) { - $lastMonthConfigQuery->where('wgp.workbenchId', $workbenchId); - } else { - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') { - $lastMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]); - break; - } - } - } - $lastMonthConfigs = $lastMonthConfigQuery->select(); - - $lastMonthPlanned = 0; - if (!empty($lastMonthConfigs)) { - $lastMonthDays = ceil(($lastMonthEnd - $lastMonthStart) / 86400); - foreach ($lastMonthConfigs as $lastMonthConfig) { - $lastMonthMaxPerDay = intval($lastMonthConfig->maxPerDay ?? 0); - $lastMonthTargetType = intval($lastMonthConfig->targetType ?? 1); - if ($lastMonthTargetType == 1) { - $lastMonthGroups = $this->decodeJsonArray($lastMonthConfig->groups ?? []); - $lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays * count($lastMonthGroups); - } else { - $lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays; - } - } - } - $lastMonthReachRate = $lastMonthPlanned > 0 ? round(($lastMonthSend / $lastMonthPlanned) * 100, 1) : 0; - - // 获取上个月的回复和点击统计(简化处理) - $lastMonthSentItems = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($lastMonthWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - $lastMonthReplyStats = $this->calculateReplyStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd); - $lastMonthClickStats = $this->calculateClickStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd); - - return [ - 'reachRate' => [ - 'value' => $reachRate, - 'trend' => round($reachRate - $lastMonthReachRate, 1), - 'unit' => '%', - 'description' => '成功发送/计划发送' - ], - 'replyRate' => [ - 'value' => $replyStats['replyRate'], - 'trend' => round($replyStats['replyRate'] - $lastMonthReplyStats['replyRate'], 1), - 'unit' => '%', - 'description' => '收到回复/成功发送' - ], - 'avgReplyTime' => [ - 'value' => $replyStats['avgReplyTime'], - 'trend' => round($lastMonthReplyStats['avgReplyTime'] - $replyStats['avgReplyTime'], 0), - 'unit' => '分钟', - 'description' => '从发送到回复的平均时长' - ], - 'clickRate' => [ - 'value' => $clickStats['clickRate'], - 'trend' => round($clickStats['clickRate'] - $lastMonthClickStats['clickRate'], 1), - 'unit' => '%', - 'description' => '点击链接/成功发送' - ], - 'plannedSend' => $plannedSend, - 'successSend' => $successSend, - 'replyCount' => $replyStats['replyCount'], - 'clickCount' => $clickStats['clickCount'] - ]; - } - - /** - * 计算回复统计 - */ - private function calculateReplyStats($sentItems, $targetType, $startTime, $endTime) - { - if (empty($sentItems)) { - return ['replyRate' => 0, 'avgReplyTime' => 0, 'replyCount' => 0]; - } - - $replyCount = 0; - $totalReplyTime = 0; - $replyTimes = []; - - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $sendTime = $itemArray['createTime'] ?? 0; - $accountId = $itemArray['wechatAccountId'] ?? 0; - - if ($targetType == 1) { - // 群推送:查找群内回复消息 - $groupId = $itemArray['groupId'] ?? 0; - $group = Db::name('wechat_group')->where('id', $groupId)->find(); - if ($group) { - $replyMsg = Db::table('s2_wechat_message') - ->where('wechatChatroomId', $group['chatroomId']) - ->where('wechatAccountId', $accountId) - ->where('isSend', 0) // 接收的消息 - ->where('wechatTime', '>', $sendTime) - ->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复 - ->order('wechatTime', 'asc') - ->find(); - - if ($replyMsg) { - $replyCount++; - $replyTime = $replyMsg['wechatTime'] - $sendTime; - $replyTimes[] = $replyTime; - $totalReplyTime += $replyTime; - } - } - } else { - // 好友推送:查找好友回复消息 - $friendId = $itemArray['friendId'] ?? 0; - $friend = Db::table('s2_wechat_friend')->where('id', $friendId)->find(); - if ($friend) { - $replyMsg = Db::table('s2_wechat_message') - ->where('wechatFriendId', $friendId) - ->where('wechatAccountId', $accountId) - ->where('isSend', 0) // 接收的消息 - ->where('wechatTime', '>', $sendTime) - ->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复 - ->order('wechatTime', 'asc') - ->find(); - - if ($replyMsg) { - $replyCount++; - $replyTime = $replyMsg['wechatTime'] - $sendTime; - $replyTimes[] = $replyTime; - $totalReplyTime += $replyTime; - } - } - } - } - - $successSend = count($sentItems); - $replyRate = $successSend > 0 ? round(($replyCount / $successSend) * 100, 1) : 0; - $avgReplyTime = $replyCount > 0 ? round(($totalReplyTime / $replyCount) / 60, 0) : 0; // 转换为分钟 - - return [ - 'replyRate' => $replyRate, - 'avgReplyTime' => $avgReplyTime, - 'replyCount' => $replyCount - ]; - } - - /** - * 计算链接点击统计 - */ - private function calculateClickStats($sentItems, $targetType, $startTime, $endTime) - { - if (empty($sentItems)) { - return ['clickRate' => 0, 'clickCount' => 0]; - } - - $clickCount = 0; - $linkContentIds = []; - - // 获取所有发送的内容ID - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $contentId = $itemArray['contentId'] ?? 0; - if ($contentId > 0) { - $linkContentIds[] = $contentId; - } - } - - if (empty($linkContentIds)) { - return ['clickRate' => 0, 'clickCount' => 0]; - } - - // 查询包含链接的内容 - $linkContents = Db::name('content_item') - ->whereIn('id', array_unique($linkContentIds)) - ->where('contentType', 2) // 链接类型 - ->column('id'); - - // 统计发送了链接内容的记录数 - $linkSendCount = 0; - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $contentId = $itemArray['contentId'] ?? 0; - if (in_array($contentId, $linkContents)) { - $linkSendCount++; - } - } - - // 简化处理:假设点击率基于链接消息的发送(实际应该从点击追踪系统获取) - // 这里可以根据业务需求调整,比如通过消息中的链接点击事件统计 - $clickCount = $linkSendCount; // 简化处理,实际需要真实的点击数据 - - $successSend = count($sentItems); - $clickRate = $successSend > 0 ? round(($clickCount / $successSend) * 100, 1) : 0; - - return [ - 'clickRate' => $clickRate, - 'clickCount' => $clickCount - ]; - } - - /** - * 获取话术组对比数据 - */ - private function getContentLibraryComparison($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - $comparison = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('content_item ci', 'ci.id = wgpi.contentId', 'left') - ->join('content_library cl', 'cl.id = ci.libraryId', 'left') - ->where($queryWhere) - ->where('cl.id', '<>', null) - ->field([ - 'cl.id as libraryId', - 'cl.name as libraryName', - 'COUNT(DISTINCT wgpi.id) as pushCount' - ]) - ->group('cl.id, cl.name') - ->select(); - - $result = []; - foreach ($comparison as $item) { - $libraryId = $item['libraryId']; - $pushCount = intval($item['pushCount']); - - // 获取该内容库的详细统计 - $libraryContentIds = Db::name('content_item') - ->where('libraryId', $libraryId) - ->column('id'); - if (empty($libraryContentIds)) { - $libraryContentIds = [-1]; - } - - $libraryWhere = array_merge($where, [['wgpi.contentId', 'in', $libraryContentIds]]); - $librarySentItems = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($libraryWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - - $config = WorkbenchGroupPush::where('workbenchId', $workbenchId)->find(); - $targetType = $config ? intval($config->targetType) : 1; - - $replyStats = $this->calculateReplyStats($librarySentItems, $targetType, $startTime, $endTime); - $clickStats = $this->calculateClickStats($librarySentItems, $targetType, $startTime, $endTime); - - // 计算转化率(简化处理,实际需要根据业务定义) - $conversionRate = $pushCount > 0 ? round(($replyStats['replyCount'] / $pushCount) * 100, 1) : 0; - - $result[] = [ - 'libraryId' => $libraryId, - 'libraryName' => $item['libraryName'], - 'pushCount' => $pushCount, - 'reachRate' => 100, // 简化处理,实际应该计算 - 'replyRate' => $replyStats['replyRate'], - 'clickRate' => $clickStats['clickRate'], - 'conversionRate' => $conversionRate, - 'avgReplyTime' => $replyStats['avgReplyTime'], - 'level' => $this->getPerformanceLevel($replyStats['replyRate'], $clickStats['clickRate'], $conversionRate) - ]; - } - - // 按回复率排序 - usort($result, function($a, $b) { - return $b['replyRate'] <=> $a['replyRate']; - }); - - return $result; - } - - /** - * 获取性能等级 - */ - private function getPerformanceLevel($replyRate, $clickRate, $conversionRate) - { - $score = ($replyRate * 0.4) + ($clickRate * 0.3) + ($conversionRate * 0.3); - - if ($score >= 40) { - return '优秀'; - } elseif ($score >= 25) { - return '良好'; - } elseif ($score >= 15) { - return '一般'; - } else { - return '待提升'; - } - } - - /** - * 获取时段分析数据 - */ - private function getTimePeriodAnalysis($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - $analysis = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->field([ - 'FROM_UNIXTIME(wgpi.createTime, "%H") as hour', - 'COUNT(*) as count' - ]) - ->group('hour') - ->order('hour', 'asc') - ->select(); - - $result = []; - foreach ($analysis as $item) { - $result[] = [ - 'hour' => intval($item['hour']), - 'count' => intval($item['count']) - ]; - } - - return $result; - } - - /** - * 获取互动深度数据 - */ - private function getInteractionDepth($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - // 简化处理,实际需要更复杂的统计逻辑 - return [ - 'singleReply' => 0, // 单次回复 - 'multipleReply' => 0, // 多次回复 - 'deepInteraction' => 0 // 深度互动 - ]; - } - - /** - * 获取推送历史记录列表 - * @return \think\response\Json - */ - public function getGroupPushHistory() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $workbenchId = $this->request->param('workbenchId', 0); - $keyword = $this->request->param('keyword', ''); - $pushType = $this->request->param('pushType', ''); // 推送类型筛选:''=全部, 'friend'=好友消息, 'group'=群消息, 'announcement'=群公告 - $status = $this->request->param('status', ''); // 状态筛选:''=全部, 'success'=已完成, 'progress'=进行中, 'failed'=失败 - $userId = $this->request->userInfo['id']; - - // 构建工作台查询条件 - $workbenchWhere = [ - ['w.userId', '=', $userId], - ['w.type', '=', self::TYPE_GROUP_PUSH], - ['w.isDel', '=', 0] - ]; - - // 如果指定了工作台ID,则验证权限并限制查询范围 - if (!empty($workbenchId)) { - $workbench = Workbench::where([ - ['id', '=', $workbenchId], - ['userId', '=', $userId], - ['type', '=', self::TYPE_GROUP_PUSH], - ['isDel', '=', 0] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - $workbenchWhere[] = ['w.id', '=', $workbenchId]; - } - - // 1. 先查询所有已执行的推送记录(按推送时间分组) - $pushHistoryQuery = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('workbench w', 'w.id = wgpi.workbenchId', 'left') - ->join('workbench_group_push wgp', 'wgp.workbenchId = wgpi.workbenchId', 'left') - ->join('content_item ci', 'ci.id = wgpi.contentId', 'left') - ->join('content_library cl', 'cl.id = ci.libraryId', 'left') - ->where($workbenchWhere) - ->field([ - 'wgpi.workbenchId', - 'w.name as workbenchName', - 'wgpi.contentId', - 'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime', - 'wgpi.targetType', - 'wgp.groupPushSubType', - 'MIN(wgpi.createTime) as createTime', - 'COUNT(DISTINCT wgpi.id) as totalCount', - 'cl.name as contentLibraryName' - ]) - ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType, wgp.groupPushSubType'); - - if (!empty($keyword)) { - $pushHistoryQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%'); - } - - $pushHistoryList = $pushHistoryQuery->order('createTime', 'desc')->select(); - - // 2. 查询所有任务(包括未执行的) - $allTasksQuery = Db::name('workbench') - ->alias('w') - ->join('workbench_group_push wgp', 'wgp.workbenchId = w.id', 'left') - ->where($workbenchWhere) - ->field([ - 'w.id as workbenchId', - 'w.name as workbenchName', - 'w.createTime', - 'wgp.targetType', - 'wgp.groupPushSubType', - 'wgp.groups', - 'wgp.friends', - 'wgp.trafficPools' - ]); - - if (!empty($keyword)) { - $allTasksQuery->where('w.name', 'like', '%' . $keyword . '%'); - } - - $allTasks = $allTasksQuery->select(); - - // 3. 合并数据:已执行的推送记录 + 未执行的任务 - $resultList = []; - $executedWorkbenchIds = []; - - // 处理已执行的推送记录 - foreach ($pushHistoryList as $item) { - $itemWorkbenchId = $item['workbenchId']; - $contentId = $item['contentId']; - $pushTime = $item['pushTime']; - $targetType = intval($item['targetType']); - $groupPushSubType = isset($item['groupPushSubType']) ? intval($item['groupPushSubType']) : 1; - - // 标记该工作台已有执行记录 - if (!in_array($itemWorkbenchId, $executedWorkbenchIds)) { - $executedWorkbenchIds[] = $itemWorkbenchId; - } - - // 将时间字符串转换为时间戳范围(小时级别) - $pushTimeStart = strtotime($pushTime); - $pushTimeEnd = $pushTimeStart + 3600; // 一小时后 - - // 获取该次推送的详细统计 - $pushWhere = [ - ['wgpi.workbenchId', '=', $itemWorkbenchId], - ['wgpi.contentId', '=', $contentId], - ['wgpi.createTime', '>=', $pushTimeStart], - ['wgpi.createTime', '<', $pushTimeEnd], - ['wgpi.targetType', '=', $targetType] - ]; - - // 目标数量 - if ($targetType == 1) { - // 群推送:统计群数量 - $targetCount = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($pushWhere) - ->where('wgpi.groupId', '<>', null) - ->distinct(true) - ->count('wgpi.groupId'); - } else { - // 好友推送:统计好友数量 - $targetCount = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($pushWhere) - ->where('wgpi.friendId', '<>', null) - ->distinct(true) - ->count('wgpi.friendId'); - } - - // 成功数和失败数(简化处理,实际需要根据发送状态判断) - $successCount = intval($item['totalCount']); // 简化处理 - $failCount = 0; // 简化处理,实际需要从发送状态获取 - - // 状态判断 - $itemStatus = $successCount > 0 ? 'success' : 'failed'; - if ($failCount > 0 && $successCount > 0) { - $itemStatus = 'partial'; - } - - // 推送类型判断 - $pushTypeText = ''; - $pushTypeCode = ''; - if ($targetType == 1) { - // 群推送 - if ($groupPushSubType == 2) { - $pushTypeText = '群公告'; - $pushTypeCode = 'announcement'; - } else { - $pushTypeText = '群消息'; - $pushTypeCode = 'group'; - } - } else { - // 好友推送 - $pushTypeText = '好友消息'; - $pushTypeCode = 'friend'; - } - - $resultList[] = [ - 'workbenchId' => $itemWorkbenchId, - 'taskName' => $item['workbenchName'] ?? '', - 'pushType' => $pushTypeText, - 'pushTypeCode' => $pushTypeCode, - 'targetCount' => $targetCount, - 'successCount' => $successCount, - 'failCount' => $failCount, - 'status' => $itemStatus, - 'statusText' => $this->getStatusText($itemStatus), - 'createTime' => date('Y-m-d H:i:s', $item['createTime']), - 'contentLibraryName' => $item['contentLibraryName'] ?? '' - ]; - } - - // 处理未执行的任务 - foreach ($allTasks as $task) { - $taskWorkbenchId = $task['workbenchId']; - - // 如果该任务已有执行记录,跳过(避免重复) - if (in_array($taskWorkbenchId, $executedWorkbenchIds)) { - continue; - } - - $targetType = isset($task['targetType']) ? intval($task['targetType']) : 1; - $groupPushSubType = isset($task['groupPushSubType']) ? intval($task['groupPushSubType']) : 1; - - // 计算目标数量(从配置中获取) - $targetCount = 0; - if ($targetType == 1) { - // 群推送:统计配置的群数量 - $groups = json_decode($task['groups'] ?? '[]', true); - $targetCount = is_array($groups) ? count($groups) : 0; - } else { - // 好友推送:统计配置的好友数量或流量池数量 - $friends = json_decode($task['friends'] ?? '[]', true); - $trafficPools = json_decode($task['trafficPools'] ?? '[]', true); - $friendCount = is_array($friends) ? count($friends) : 0; - $poolCount = is_array($trafficPools) ? count($trafficPools) : 0; - // 如果配置了流量池,目标数量暂时显示为流量池数量(实际数量需要从流量池中统计) - $targetCount = $friendCount > 0 ? $friendCount : $poolCount; - } - - // 推送类型判断 - $pushTypeText = ''; - $pushTypeCode = ''; - if ($targetType == 1) { - // 群推送 - if ($groupPushSubType == 2) { - $pushTypeText = '群公告'; - $pushTypeCode = 'announcement'; - } else { - $pushTypeText = '群消息'; - $pushTypeCode = 'group'; - } - } else { - // 好友推送 - $pushTypeText = '好友消息'; - $pushTypeCode = 'friend'; - } - - $resultList[] = [ - 'workbenchId' => $taskWorkbenchId, - 'taskName' => $task['workbenchName'] ?? '', - 'pushType' => $pushTypeText, - 'pushTypeCode' => $pushTypeCode, - 'targetCount' => $targetCount, - 'successCount' => 0, - 'failCount' => 0, - 'status' => 'pending', - 'statusText' => '进行中', - 'createTime' => date('Y-m-d H:i:s', $task['createTime']), - 'contentLibraryName' => '' - ]; - } - - // 应用筛选条件 - $filteredList = []; - foreach ($resultList as $item) { - // 推送类型筛选 - if (!empty($pushType)) { - if ($pushType === 'friend' && $item['pushTypeCode'] !== 'friend') { - continue; - } - if ($pushType === 'group' && $item['pushTypeCode'] !== 'group') { - continue; - } - if ($pushType === 'announcement' && $item['pushTypeCode'] !== 'announcement') { - continue; - } - } - - // 状态筛选 - if (!empty($status)) { - if ($status === 'success' && $item['status'] !== 'success') { - continue; - } - if ($status === 'progress') { - // 进行中:包括 partial 和 pending - if ($item['status'] !== 'partial' && $item['status'] !== 'pending') { - continue; - } - } - if ($status === 'failed' && $item['status'] !== 'failed') { - continue; - } - } - - $filteredList[] = $item; - } - - // 按创建时间倒序排序 - usort($filteredList, function($a, $b) { - return strtotime($b['createTime']) - strtotime($a['createTime']); - }); - - // 分页处理 - $total = count($filteredList); - $offset = ($page - 1) * $limit; - $list = array_slice($filteredList, $offset, $limit); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取状态文本 - * @param string $status 状态码 - * @return string 状态文本 - */ - private function getStatusText($status) - { - $statusMap = [ - 'success' => '已完成', - 'partial' => '进行中', - 'pending' => '进行中', - 'failed' => '失败' - ]; - return $statusMap[$status] ?? '未知'; - } - - /** - * 获取已创建的群列表(自动建群) - * @return \think\response\Json - */ - public function getCreatedGroupsList() - { - $workbenchId = $this->request->param('workbenchId', 0); - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 100); - $keyword = $this->request->param('keyword', ''); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 获取已创建的群ID列表(状态为成功且groupId不为空) - $groupIds = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->where('groupId', '<>', null) - ->group('groupId') - ->column('groupId'); - - if (empty($groupIds)) { - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => [], - 'total' => 0, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - // 查询群组详细信息(从s2_wechat_chatroom表查询) - $query = Db::table('s2_wechat_chatroom')->alias('wc') - ->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left') - ->where('wc.id', 'in', $groupIds) - ->where('wc.isDeleted', 0); - - // 关键字搜索 - if (!empty($keyword)) { - $query->where(function ($q) use ($keyword) { - $q->where('wc.nickname', 'like', '%' . $keyword . '%') - ->whereOr('wc.chatroomId', 'like', '%' . $keyword . '%') - ->whereOr('wa.nickName', 'like', '%' . $keyword . '%'); - }); - } - - $total = $query->count(); - $list = $query->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias') - ->order('wc.createTime', 'desc') - ->page($page, $limit) - ->select(); - - // 统计每个群的成员数量和成员信息 - foreach ($list as &$item) { - // 统计该群的成员数量(从workbench_group_create_item表统计) - $memberCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $item['id']) - ->where('status', 'in', [2, 4]) // 创建成功和管理员好友已拉入 - ->count(); - - // 获取成员列表(用于显示成员头像) - $memberList = Db::name('workbench_group_create_item')->alias('wgci') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left') - ->where('wgci.workbenchId', $workbenchId) - ->where('wgci.groupId', $item['id']) - ->where('wgci.status', 'in', [2, 4]) - ->field('wf.avatar, wf.wechatId, wf.nickname') - ->order('wgci.createTime', 'asc') - ->limit(10) // 最多显示10个成员头像 - ->select(); - - // 格式化成员头像列表 - $memberAvatars = []; - foreach ($memberList as $member) { - if (!empty($member['avatar'])) { - $memberAvatars[] = [ - 'avatar' => $member['avatar'], - 'wechatId' => $member['wechatId'] ?? '', - 'nickname' => $member['nickname'] ?? '' - ]; - } - } - - // 计算剩余成员数(用于显示"+XX") - $remainingCount = $memberCount > count($memberAvatars) ? $memberCount - count($memberAvatars) : 0; - - // 格式化返回数据 - $item['memberCount'] = $memberCount; - $item['memberCountText'] = $memberCount . '人'; // 格式化为"XX人" - $item['createTime'] = !empty($item['createTime']) ? date('Y-m-d', $item['createTime']) : ''; // 格式化为"YYYY-MM-DD" - $item['memberAvatars'] = $memberAvatars; // 成员头像列表(最多10个) - $item['remainingCount'] = $remainingCount; // 剩余成员数(用于显示"+XX") - - // 保留原有字段,但调整格式 - $item['groupName'] = $item['groupName'] ?? ''; - $item['groupAvatar'] = $item['groupAvatar'] ?? ''; - } - unset($item); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取已创建群的详情(自动建群) - * @return \think\response\Json - */ - public function getCreatedGroupDetail() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 验证该群是否属于该工作台 - $groupItem = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $groupId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->find(); - - if (empty($groupItem)) { - return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']); - } - - // 查询群基本信息(从s2_wechat_chatroom表查询) - $group = Db::table('s2_wechat_chatroom')->alias('wc') - ->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left') - ->where('wc.id', $groupId) - ->where('wc.isDeleted', 0) - ->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias,wc.announce') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - // 获取chatroomId - $chatroomId = $group['chatroomId'] ?? ''; - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - // 从s2_wechat_chatroom_member表查询所有成员列表(不限制数量) - $memberList = Db::table('s2_wechat_chatroom_member')->alias('wcm') - ->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = wcm.wechatId', 'left') - ->where('wcm.chatroomId', $chatroomId) - ->field('wcm.wechatId,wcm.nickname as memberNickname,wcm.avatar as memberAvatar,wcm.conRemark as memberRemark,wcm.alias as memberAlias,wcm.createTime as joinTime,wcm.updateTime,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar') - ->order('wcm.createTime', 'asc') - ->select(); - - // 去重:按wechatId去重,保留第一条记录 - $memberMap = []; - foreach ($memberList as $member) { - $wechatId = $member['wechatId'] ?? ''; - if (!empty($wechatId) && !isset($memberMap[$wechatId])) { - $memberMap[$wechatId] = $member; - } - } - $memberList = array_values($memberMap); // 重新索引数组 - - // 获取在群中的成员wechatId列表(用于判断是否已退群) - $inGroupWechatIds = array_column($memberList, 'wechatId'); - $inGroupWechatIds = array_filter($inGroupWechatIds); // 过滤空值 - - // 获取通过自动建群加入的成员信息(用于判断入群状态和已退群成员) - $autoJoinMemberList = Db::name('workbench_group_create_item') - ->alias('wgci') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left') - ->where('wgci.workbenchId', $workbenchId) - ->where('wgci.groupId', $groupId) - ->where('wgci.status', 'in', [2, 4]) // 创建成功和管理员好友已拉入 - ->field('wf.wechatId,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar,wgci.createTime as autoJoinTime') - ->select(); - - // 去重:按wechatId去重,保留第一条记录 - $autoJoinMemberMap = []; - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - if (!empty($wechatId) && !isset($autoJoinMemberMap[$wechatId])) { - $autoJoinMemberMap[$wechatId] = $autoMember; - } - } - $autoJoinMemberList = array_values($autoJoinMemberMap); // 重新索引数组 - - // 统计成员总数(包括在群中的成员和已退群的成员) - // 先统计在群中的成员数 - $inGroupCount = count($memberList); - - // 统计已退群的成员数(在自动建群记录中但不在群中的成员) - $quitCount = 0; - if (!empty($autoJoinMemberList)) { - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - if (!empty($wechatId) && !in_array($wechatId, $inGroupWechatIds)) { - $quitCount++; - } - } - } - - // 总成员数 = 在群中的成员数 + 已退群的成员数 - $memberCount = $inGroupCount + $quitCount; - - // 获取自动建群加入的成员wechatId列表(用于判断入群状态) - $autoJoinWechatIds = array_column($autoJoinMemberList, 'wechatId'); - $autoJoinWechatIds = array_filter($autoJoinWechatIds); // 过滤空值 - - // 格式化在群中的成员列表 - $members = []; - $addedWechatIds = []; // 用于记录已添加的成员wechatId,避免重复 - $ownerWechatId = $group['ownerWechatId'] ?? ''; - foreach ($memberList as $member) { - $wechatId = $member['wechatId'] ?? ''; - - // 跳过空wechatId和已添加的成员 - if (empty($wechatId) || in_array($wechatId, $addedWechatIds)) { - continue; - } - - // 标记为已添加 - $addedWechatIds[] = $wechatId; - - // 根据wechatId判断是否为群主 - $isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0; - - // 判断入群状态:如果在自动建群记录中,说明是通过自动建群加入的;否则是其他方式加入的 - $joinStatus = in_array($wechatId, $autoJoinWechatIds) ? 'auto' : 'manual'; - - // 判断是否已退群:如果成员在s2_wechat_chatroom_member表中存在,说明在群中;否则已退群 - // 由于我们已经从s2_wechat_chatroom_member表查询,所以这里都是"在群中"状态 - $isQuit = 0; // 0=在群中,1=已退群 - - // 优先使用friend表的昵称和头像,如果没有则使用member表的 - $nickname = !empty($member['friendNickname']) ? $member['friendNickname'] : ($member['memberNickname'] ?? ''); - $avatar = !empty($member['friendAvatar']) ? $member['friendAvatar'] : ($member['memberAvatar'] ?? ''); - - $members[] = [ - 'friendId' => $member['friendId'] ?? 0, - 'wechatId' => $wechatId, - 'nickname' => $nickname, - 'avatar' => $avatar, - 'alias' => $member['memberAlias'] ?? '', - 'remark' => $member['memberRemark'] ?? '', - 'isOwner' => $isOwner, // 标记群主 - 'joinStatus' => $joinStatus, // 入群状态:auto=自动建群加入,manual=其他方式加入 - 'isQuit' => $isQuit, // 是否已退群:0=在群中,1=已退群 - 'joinTime' => !empty($member['joinTime']) ? date('Y-m-d H:i:s', $member['joinTime']) : '', // 入群时间 - ]; - } - - // 添加已退群的成员(在自动建群记录中但不在群中的成员) - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - - // 跳过空wechatId、已在群中的成员和已添加的成员 - if (empty($wechatId) || in_array($wechatId, $inGroupWechatIds) || in_array($wechatId, $addedWechatIds)) { - continue; - } - - // 标记为已添加 - $addedWechatIds[] = $wechatId; - - // 根据wechatId判断是否为群主 - $isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0; - - $members[] = [ - 'friendId' => $autoMember['friendId'] ?? 0, - 'wechatId' => $wechatId, - 'nickname' => $autoMember['friendNickname'] ?? '', - 'avatar' => $autoMember['friendAvatar'] ?? '', - 'alias' => '', - 'remark' => '', - 'isOwner' => $isOwner, // 标记群主 - 'joinStatus' => 'auto', // 入群状态:auto=自动建群加入 - 'isQuit' => 1, // 是否已退群:1=已退群 - 'joinTime' => !empty($autoMember['autoJoinTime']) ? date('Y-m-d H:i:s', $autoMember['autoJoinTime']) : '', // 入群时间 - ]; - } - - // 将群主排在第一位 - usort($members, function($a, $b) { - if ($a['isOwner'] == $b['isOwner']) { - return 0; - } - return $a['isOwner'] > $b['isOwner'] ? -1 : 1; - }); - - // 格式化返回数据 - $result = [ - 'id' => $group['id'], - 'groupName' => $group['groupName'] ?? '', - 'chatroomId' => $group['chatroomId'] ?? '', - 'groupAvatar' => $group['groupAvatar'] ?? '', - 'ownerWechatId' => $group['ownerWechatId'] ?? '', - 'ownerNickname' => $group['ownerNickname'] ?? '', - 'ownerAvatar' => $group['ownerAvatar'] ?? '', - 'ownerAlias' => $group['ownerAlias'] ?? '', - 'announce' => $group['announce'] ?? '', - 'createTime' => !empty($group['createTime']) ? date('Y-m-d H:i', $group['createTime']) : '', // 格式化为"YYYY-MM-DD HH:MM" - 'memberCount' => $memberCount, - 'memberCountText' => $memberCount . '人', // 格式化为"XX人" - 'workbenchName' => $workbench->name ?? '', // 任务名称(工作台名称) - 'members' => $members // 所有成员列表 - ]; - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => $result - ]); - } - - /** - * 同步群最新信息(包括群成员) - * @return \think\response\Json - */ - public function syncGroupInfo() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 验证该群是否属于该工作台 - $groupItem = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $groupId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->find(); - - if (empty($groupItem)) { - return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']); - } - - // 查询群基本信息,获取chatroomId和wechatAccountWechatId - $group = Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->where('isDeleted', 0) - ->field('id,chatroomId,wechatAccountWechatId') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - $chatroomId = $group['chatroomId'] ?? ''; - $wechatAccountWechatId = $group['wechatAccountWechatId'] ?? ''; - - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - try { - // 实例化WechatChatroomController - $chatroomController = new \app\api\controller\WechatChatroomController(); - - // 1. 同步群信息(调用getlist方法) - $syncData = [ - 'wechatChatroomId' => $chatroomId, // 使用chatroomId作为wechatChatroomId来指定要同步的群 - 'wechatAccountKeyword' => $wechatAccountWechatId, // 通过群主微信ID筛选 - 'isDeleted' => false, - 'pageIndex' => 1, - 'pageSize' => 100 // 获取足够多的数据 - ]; - $syncResult = $chatroomController->getlist($syncData, true, 0); // isInner = true - $syncResponse = json_decode($syncResult, true); - - if (empty($syncResponse['code']) || $syncResponse['code'] != 200) { - return json(['code' => 500, 'msg' => '同步群信息失败:' . ($syncResponse['msg'] ?? '未知错误')]); - } - - // 2. 同步群成员信息(调用listChatroomMember方法) - // wechatChatroomId 使用 s2_wechat_chatroom 表的 id(即groupId) - // chatroomId 使用群聊ID(chatroomId) - $wechatChatroomId = $groupId; // s2_wechat_chatroom表的id - $memberSyncResult = $chatroomController->listChatroomMember($wechatChatroomId, $chatroomId, true); // isInner = true - $memberSyncResponse = json_decode($memberSyncResult, true); - - if (empty($memberSyncResponse['code']) || $memberSyncResponse['code'] != 200) { - // 成员同步失败不影响整体结果,记录警告即可 - \think\facade\Log::warning("同步群成员失败。群ID: {$groupId}, 群聊ID: {$chatroomId}, 错误: " . ($memberSyncResponse['msg'] ?? '未知错误')); - } - - return json([ - 'code' => 200, - 'msg' => '同步成功', - 'data' => [ - 'groupId' => $groupId, - 'chatroomId' => $chatroomId, - 'groupInfoSynced' => true, - 'memberInfoSynced' => !empty($memberSyncResponse['code']) && $memberSyncResponse['code'] == 200 - ] - ]); - } catch (\Exception $e) { - \think\facade\Log::error("同步群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage()); - return json(['code' => 500, 'msg' => '同步失败:' . $e->getMessage()]); - } - } - - /** - * 修改群名称、群公告 - * @return \think\response\Json - */ - public function modifyGroupInfo() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - $chatroomName = $this->request->param('chatroomName', ''); - $announce = $this->request->param('announce', ''); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 至少需要提供一个修改项 - if (empty($chatroomName) && empty($announce)) { - return json(['code' => 400, 'msg' => '请至少提供群名称或群公告中的一个参数']); - } - - // 查询群基本信息 - $group = Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->where('isDeleted', 0) - ->field('id,chatroomId,wechatAccountWechatId,accountId,wechatAccountId') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - $chatroomId = $group['id'] ?? ''; - - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - try { - // 直接使用群表中的账号信息 - $executeAccountId = $group['accountId'] ?? 0; - $executeWechatAccountId = $group['wechatAccountId'] ?? 0; - $executeWechatId = $group['wechatAccountWechatId'] ?? ''; - - // 确保 wechatId 不为空 - if (empty($executeWechatId)) { - return json(['code' => 400, 'msg' => '无法获取微信账号ID']); - } - - // 调用 WebSocketController 修改群信息 - // 获取系统API账号信息(用于WebSocket连接) - $username = Env::get('api.username2', ''); - $password = Env::get('api.password2', ''); - - if (empty($username) || empty($password)) { - return json(['code' => 500, 'msg' => '系统API账号配置缺失']); - } - - // 获取系统账号ID - $systemAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); - if (empty($systemAccountId)) { - return json(['code' => 500, 'msg' => '未找到系统账号ID']); - } - - $webSocketController = new WebSocketController([ - 'userName' => $username, - 'password' => $password, - 'accountId' => $systemAccountId - ]); - // 构建修改参数 - $modifyData = [ - 'wechatChatroomId' => $chatroomId, - 'wechatAccountId' => $executeWechatAccountId, - ]; - if (!empty($chatroomName)) { - $modifyData['chatroomName'] = $chatroomName; - } - if (!empty($announce)) { - $modifyData['announce'] = $announce; - } - - - $modifyResult = $webSocketController->CmdChatroomModifyInfo($modifyData); - $modifyResponse = json_decode($modifyResult, true); - if (empty($modifyResponse['code']) || $modifyResponse['code'] != 200) { - return json(['code' => 500, 'msg' => '修改群信息失败:' . ($modifyResponse['msg'] ?? '未知错误')]); - } - - // 修改成功后更新数据库 - $updateData = [ - 'updateTime' => time() - ]; - - // 如果修改了群名称,更新数据库 - if (!empty($chatroomName)) { - $updateData['nickname'] = $chatroomName; - } - - // 如果修改了群公告,更新数据库 - if (!empty($announce)) { - $updateData['announce'] = $announce; - } - - // 更新数据库 - Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->update($updateData); - - return json([ - 'code' => 200, - 'msg' => '修改成功', - 'data' => [ - 'groupId' => $groupId, - 'chatroomId' => $chatroomId, - 'chatroomName' => $chatroomName, - 'announce' => $announce - ] - ]); - } catch (\Exception $e) { - \think\facade\Log::error("修改群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage()); - return json(['code' => 500, 'msg' => '修改失败:' . $e->getMessage()]); - } - } - - /** - * 退群功能 - * @return \think\response\Json - */ - public function 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()]); - } - } - - /** - * 转移群聊到指定账号 - * @param int $groupId 群ID(s2_wechat_chatroom表的id) - * @param string $chatroomId 群聊ID - * @param int $toAccountId 目标账号ID(s2_company_account表的id) - * @param int $toWechatAccountId 目标微信账号ID(s2_wechat_account表的id) - * @return array ['success' => bool, 'msg' => string] - */ - protected function transferChatroomToAccount($groupId, $chatroomId, $toAccountId, $toWechatAccountId) - { - try { - // 查询目标账号信息 - $targetAccount = Db::table('s2_company_account') - ->where('id', $toAccountId) - ->field('id,userName,realName,nickname') - ->find(); - - if (empty($targetAccount)) { - return ['success' => false, 'msg' => '目标账号不存在']; - } - - // 查询目标微信账号信息 - $targetWechatAccount = Db::table('s2_wechat_account') - ->where('id', $toWechatAccountId) - ->field('id,wechatId,deviceAccountId') - ->find(); - - if (empty($targetWechatAccount)) { - return ['success' => false, 'msg' => '目标微信账号不存在']; - } - - // 调用 AutomaticAssign 进行群聊转移 - $automaticAssign = new \app\api\controller\AutomaticAssign(); - - // 构建转移参数(通过 API 调用) - $transferData = [ - 'wechatChatroomId' => $chatroomId, // 使用群聊ID - 'toAccountId' => $toAccountId, - 'wechatAccountKeyword' => $targetWechatAccount['wechatId'] ?? '', - 'isDeleted' => false - ]; - - // 直接更新数据库(因为 API 可能不支持指定单个群聊ID转移) - // 更新 s2_wechat_chatroom 表的 accountId 和 wechatAccountId - Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->update([ - 'accountId' => $toAccountId, - 'accountUserName' => $targetAccount['userName'] ?? '', - 'accountRealName' => $targetAccount['realName'] ?? '', - 'accountNickname' => $targetAccount['nickname'] ?? '', - 'wechatAccountId' => $toWechatAccountId, - 'wechatAccountWechatId' => $targetWechatAccount['wechatId'] ?? '', - 'updateTime' => time() - ]); - - return ['success' => true, 'msg' => '转移成功']; - } catch (\Exception $e) { - \think\facade\Log::error("转移群聊异常。群ID: {$groupId}, 目标账号ID: {$toAccountId}, 错误: " . $e->getMessage()); - return ['success' => false, 'msg' => $e->getMessage()]; - } - } - - -} \ No newline at end of file +} diff --git a/Server/application/cunkebao/controller/workbench/WorkbenchGroupPushController.php b/Server/application/cunkebao/controller/workbench/WorkbenchGroupPushController.php index e7a30d42..73edb5a5 100644 --- a/Server/application/cunkebao/controller/workbench/WorkbenchGroupPushController.php +++ b/Server/application/cunkebao/controller/workbench/WorkbenchGroupPushController.php @@ -2,13 +2,13 @@ namespace app\cunkebao\controller\workbench; -use app\cunkebao\model\Workbench; -use app\cunkebao\model\WorkbenchGroupPush; use think\Controller; -use think\Db; /** * 工作台 - 群消息推送相关功能 + * + * 注意:此控制器中的方法已迁移至 WorkbenchController + * 保留此文件仅用于类型常量定义,实际路由指向 WorkbenchController */ class WorkbenchGroupPushController extends Controller { @@ -16,3619 +16,4 @@ class WorkbenchGroupPushController extends Controller * 工作台类型定义 */ const TYPE_GROUP_PUSH = 3; // 群消息推送 - - /** - * 获取群发统计数据 - * @return \think\response\Json - */ - public function getGroupPushStats() - { - $workbenchId = $this->request->param('workbenchId', 0); - $timeRange = $this->request->param('timeRange', '7'); // 默认最近7天 - $contentLibraryIds = $this->request->param('contentLibraryIds', ''); // 话术组筛选 - $userId = $this->request->userInfo['id']; - - // 如果指定了工作台ID,则验证权限 - if (!empty($workbenchId)) { - $workbench = Workbench::where([ - ['id', '=', $workbenchId], - ['userId', '=', $userId], - ['type', '=', self::TYPE_GROUP_PUSH], - ['isDel', '=', 0] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - } - - // 计算时间范围 - $days = intval($timeRange); - $startTime = strtotime(date('Y-m-d 00:00:00', strtotime("-{$days} days"))); - $endTime = time(); - - // 构建查询条件 - $where = [ - ['wgpi.createTime', '>=', $startTime], - ['wgpi.createTime', '<=', $endTime] - ]; - { - $id = $this->request->param('id', ''); - - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 定义关联关系 - $with = [ - 'autoLike' => function ($query) { - $query->field('workbenchId,interval,maxLikes,startTime,endTime,contentTypes,devices,friends,friendMaxLikes,friendTags,enableFriendTags'); - }, - 'momentsSync' => function ($query) { - $query->field('workbenchId,syncInterval,syncCount,syncType,startTime,endTime,accountType,devices,contentLibraries'); - }, - 'trafficConfig' => function ($query) { - $query->field('workbenchId,distributeType,maxPerDay,timeType,startTime,endTime,devices,pools,account'); - }, - 'groupPush' => function ($query) { - $query->field('workbenchId,pushType,targetType,groupPushSubType,startTime,endTime,maxPerDay,pushOrder,isLoop,status,groups,friends,ownerWechatIds,trafficPools,contentLibraries,friendIntervalMin,friendIntervalMax,messageIntervalMin,messageIntervalMax,isRandomTemplate,postPushTags,announcementContent,enableAiRewrite,aiRewritePrompt'); - }, - 'groupCreate' => function ($query) { - $query->field('workbenchId,devices,startTime,endTime,groupSizeMin,groupSizeMax,maxGroupsPerDay,groupNameTemplate,groupDescription,poolGroups,wechatGroups,admins'); - }, - 'importContact' => function ($query) { - $query->field('workbenchId,devices,pools,num,remarkType,remark,clearContact,startTime,endTime'); - }, - ]; - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - - $workbench = Workbench::where($where) - ->field('id,name,type,status,autoStart,createTime,updateTime,companyId') - ->with($with) - ->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - // 处理配置信息 - switch ($workbench->type) { - //自动点赞 - case self::TYPE_AUTO_LIKE: - if (!empty($workbench->autoLike)) { - $workbench->config = $workbench->autoLike; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->wechatFriends = json_decode($workbench->config->friends, true); - $workbench->config->targetType = 2; - //$workbench->config->targetGroups = json_decode($workbench->config->targetGroups, true); - $workbench->config->contentTypes = json_decode($workbench->config->contentTypes, true); - - // 添加今日点赞数 - $startTime = strtotime(date('Y-m-d') . ' 00:00:00'); - $endTime = strtotime(date('Y-m-d') . ' 23:59:59'); - $todayLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $workbench->id) - ->whereTime('createTime', 'between', [$startTime, $endTime]) - ->count(); - - // 添加总点赞数 - $totalLikeCount = Db::name('workbench_auto_like_item') - ->where('workbenchId', $workbench->id) - ->count(); - - $workbench->config->todayLikeCount = $todayLikeCount; - $workbench->config->totalLikeCount = $totalLikeCount; - - unset($workbench->autoLike, $workbench->auto_like); - } - break; - //自动同步朋友圈 - case self::TYPE_MOMENTS_SYNC: - if (!empty($workbench->momentsSync)) { - $workbench->config = $workbench->momentsSync; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->contentGroups = json_decode($workbench->config->contentLibraries, true); - - //同步记录 - $sendNum = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->count(); - $workbench->syncCount = $sendNum; - $lastTime = Db::name('workbench_moments_sync_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->value('createTime'); - $workbench->lastSyncTime = !empty($lastTime) ? date('Y-m-d H:i', $lastTime) : '--'; - unset($workbench->momentsSync, $workbench->moments_sync); - } - break; - //群推送 - case self::TYPE_GROUP_PUSH: - if (!empty($workbench->groupPush)) { - $workbench->config = $workbench->groupPush; - $workbench->config->targetType = isset($workbench->config->targetType) ? intval($workbench->config->targetType) : 1; // 默认1=群推送 - $workbench->config->groupPushSubType = isset($workbench->config->groupPushSubType) ? intval($workbench->config->groupPushSubType) : 1; // 默认1=群群发 - $workbench->config->ownerWechatIds = json_decode($workbench->config->ownerWechatIds ?? '[]', true) ?: []; - // 根据targetType解析不同的数据 - if ($workbench->config->targetType == 1) { - // 群推送 - $workbench->config->wechatGroups = json_decode($workbench->config->groups, true) ?: []; - $workbench->config->wechatFriends = []; - $workbench->config->trafficPools = []; - // 群推送不需要devices字段 - // 群公告相关字段 - if ($workbench->config->groupPushSubType == 2) { - $workbench->config->announcementContent = isset($workbench->config->announcementContent) ? $workbench->config->announcementContent : ''; - $workbench->config->enableAiRewrite = isset($workbench->config->enableAiRewrite) ? intval($workbench->config->enableAiRewrite) : 0; - $workbench->config->aiRewritePrompt = isset($workbench->config->aiRewritePrompt) ? $workbench->config->aiRewritePrompt : ''; - } - } else { - // 好友推送 - $workbench->config->wechatFriends = json_decode($workbench->config->friends, true) ?: []; - $workbench->config->wechatGroups = []; - $workbench->config->trafficPools = json_decode($workbench->config->trafficPools ?? '[]', true) ?: []; - } - $workbench->config->contentLibraries = json_decode($workbench->config->contentLibraries, true); - $workbench->config->postPushTags = json_decode($workbench->config->postPushTags ?? '[]', true) ?: []; - unset($workbench->groupPush, $workbench->group_push); - } - break; - //建群助手 - case self::TYPE_GROUP_CREATE: - if (!empty($workbench->groupCreate)) { - $workbench->config = $workbench->groupCreate; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->poolGroups = json_decode($workbench->config->poolGroups, true); - $workbench->config->wechatGroups = json_decode($workbench->config->wechatGroups, true); - $workbench->config->admins = json_decode($workbench->config->admins ?? '[]', true) ?: []; - - // 处理群管理员相关字段 - $workbench->config->groupAdminEnabled = !empty($workbench->config->admins) ? 1 : 0; - - // 如果有管理员,设置groupAdminWechatId为第一个管理员的ID(用于前端回显) - $workbench->config->groupAdminWechatId = !empty($workbench->config->admins) ? $workbench->config->admins[0] : null; - - // 统计已建群数(状态为成功且groupId不为空的记录,按groupId分组去重) - $createdGroupsCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbench->id) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->where('groupId', '<>', null) - ->group('groupId') - ->count(); - - // 统计总人数(该工作台的所有记录数) - $totalMembersCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbench->id) - ->count(); - - // 添加统计信息 - $workbench->config->stats = [ - 'createdGroupsCount' => $createdGroupsCount, - 'totalMembersCount' => $totalMembersCount - ]; - - unset($workbench->groupCreate, $workbench->group_create); - } - break; - //流量分发 - case self::TYPE_TRAFFIC_DISTRIBUTION: - if (!empty($workbench->trafficConfig)) { - $workbench->config = $workbench->trafficConfig; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->accountGroups = json_decode($workbench->config->account, true); - $workbench->config->poolGroups = json_decode($workbench->config->pools, true); - $config_item = Db::name('workbench_traffic_config_item')->where(['workbenchId' => $workbench->id])->order('id DESC')->find(); - $workbench->config->lastUpdated = !empty($config_item) ? date('Y-m-d H:i', $config_item['createTime']) : '--'; - - //统计 - $labels = $workbench->config->poolGroups; - $totalUsers = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_company_account' => 'sa'], 'sa.id = wf.accountId', 'left') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where([ - ['wf.isDeleted', '=', 0], - ['sa.departmentId', '=', $workbench->companyId] - ]) - ->whereIn('wa.currentDeviceId', $workbench->config->deviceGroups) - ->field('wf.id,wf.wechatAccountId,wf.wechatId,wf.labels,sa.userName,wa.currentDeviceId as deviceId') - ->where(function ($q) use ($labels) { - foreach ($labels as $label) { - $q->whereOrRaw("JSON_CONTAINS(wf.labels, '\"{$label}\"')"); - } - })->count(); - - $totalAccounts = Db::table('s2_company_account') - ->alias('a') - ->where(['a.departmentId' => $workbench->companyId, 'a.status' => 0]) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%') - ->group('a.id') - ->count(); - - $dailyAverage = Db::name('workbench_traffic_config_item') - ->where('workbenchId', $workbench->id) - ->count(); - $day = (time() - strtotime($workbench->createTime)) / 86400; - $day = intval($day); - - - if ($dailyAverage > 0) { - $dailyAverage = $dailyAverage / $totalAccounts / $day; - } - - $workbench->config->total = [ - 'dailyAverage' => intval($dailyAverage), - 'totalAccounts' => $totalAccounts, - 'deviceCount' => count($workbench->config->deviceGroups), - 'poolCount' => count($workbench->config->poolGroups), - 'totalUsers' => $totalUsers >> 0 - ]; - unset($workbench->trafficConfig, $workbench->traffic_config); - } - break; - case self::TYPE_IMPORT_CONTACT: - if (!empty($workbench->importContact)) { - $workbench->config = $workbench->importContact; - $workbench->config->deviceGroups = json_decode($workbench->config->devices, true); - $workbench->config->poolGroups = json_decode($workbench->config->pools, true); - } - unset($workbench->importContact, $workbench->import_contact); - break; - } - unset( - $workbench->autoLike, - $workbench->momentsSync, - $workbench->groupPush, - $workbench->groupCreate, - $workbench->config->devices, - $workbench->config->friends, - $workbench->config->groups, - $workbench->config->contentLibraries, - $workbench->config->account, - ); - - - //获取设备信息 - if (!empty($workbench->config->deviceGroups)) { - $deviceList = DeviceModel::alias('d') - ->field([ - 'd.id', 'd.imei', 'd.memo', 'd.alive', - 'l.wechatId', - 'a.nickname', 'a.alias', 'a.avatar', 'a.alias', '0 totalFriend' - ]) - ->leftJoin('device_wechat_login l', 'd.id = l.deviceId and l.alive =' . DeviceWechatLoginModel::ALIVE_WECHAT_ACTIVE . ' and l.companyId = d.companyId') - ->leftJoin('wechat_account a', 'l.wechatId = a.wechatId') - ->whereIn('d.id', $workbench->config->deviceGroups) - ->order('d.id desc') - ->select(); - - foreach ($deviceList as &$device) { - $curstomer = WechatCustomerModel::field('friendShip')->where(['wechatId' => $device['wechatId']])->find(); - $device['totalFriend'] = $curstomer->friendShip->totalFriend ?? 0; - } - unset($device); - - $workbench->config->deviceGroupsOptions = $deviceList; - } else { - $workbench->config->deviceGroupsOptions = []; - } - - - // 获取群(当targetType=1时) - if (!empty($workbench->config->wechatGroups) && isset($workbench->config->targetType) && $workbench->config->targetType == 1) { - $groupList = Db::name('wechat_group')->alias('wg') - ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') - ->where('wg.id', 'in', $workbench->config->wechatGroups) - ->order('wg.id', 'desc') - ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wa.avatar,wa.alias,wg.avatar as groupAvatar') - ->select(); - $workbench->config->wechatGroupsOptions = $groupList; - } else { - $workbench->config->wechatGroupsOptions = []; - } - - // 获取好友(当targetType=2时) - if (!empty($workbench->config->wechatFriends) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { - $friendList = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where('wf.id', 'in', $workbench->config->wechatFriends) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar') - ->select(); - $workbench->config->wechatFriendsOptions = $friendList; - } else { - $workbench->config->wechatFriendsOptions = []; - } - - // 获取流量池(当targetType=2时) - if (!empty($workbench->config->trafficPools) && isset($workbench->config->targetType) && $workbench->config->targetType == 2) { - $poolList = Db::name('traffic_source_package')->alias('tsp') - ->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0') - ->whereIn('tsp.id', $workbench->config->trafficPools) - ->where('tsp.isDel', 0) - ->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0]) - ->field('tsp.id,tsp.name,tsp.description,tsp.pic,COUNT(tspi.id) as itemCount') - ->group('tsp.id') - ->order('tsp.id', 'desc') - ->select(); - $workbench->config->trafficPoolsOptions = $poolList; - } else { - $workbench->config->trafficPoolsOptions = []; - } - - // 获取内容库名称 - if (!empty($workbench->config->contentGroups)) { - $libraryNames = ContentLibrary::where('id', 'in', $workbench->config->contentGroups)->select(); - $workbench->config->contentGroupsOptions = $libraryNames; - } else { - $workbench->config->contentGroupsOptions = []; - } - - //账号 - if (!empty($workbench->config->accountGroups)) { - $account = Db::table('s2_company_account')->alias('a') - ->where(['a.departmentId' => $this->request->userInfo['companyId'], 'a.status' => 0]) - ->whereIn('a.id', $workbench->config->accountGroups) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%') - ->field('a.id,a.userName,a.realName,a.nickname,a.memo') - ->select(); - $workbench->config->accountGroupsOptions = $account; - } else { - $workbench->config->accountGroupsOptions = []; - } - - if (!empty($workbench->config->poolGroups)) { - $poolGroupsOptions = Db::name('traffic_source_package')->alias('tsp') - ->join('traffic_source_package_item tspi', 'tspi.packageId=tsp.id', 'left') - ->whereIn('tsp.companyId', [$this->request->userInfo['companyId'], 0]) - ->whereIn('tsp.id', $workbench->config->poolGroups) - ->field('tsp.id,tsp.name,tsp.description,tsp.createTime,count(tspi.id) as num') - ->group('tsp.id') - ->select(); - $workbench->config->poolGroupsOptions = $poolGroupsOptions; - } else { - $workbench->config->poolGroupsOptions = []; - } - - if (!empty($workbench->config->ownerWechatIds)) { - $ownerWechatOptions = Db::name('wechat_account') - ->whereIn('id', $workbench->config->ownerWechatIds) - ->field('id,wechatId,nickName,avatar,alias') - ->select(); - $workbench->config->ownerWechatOptions = $ownerWechatOptions; - } else { - $workbench->config->ownerWechatOptions = []; - } - - // 获取群组选项(自动建群) - if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->wechatGroups)) { - // 分离数字ID(好友ID)和字符串ID(手动创建的群组) - $friendIds = []; - $manualGroupIds = []; - - foreach ($workbench->config->wechatGroups as $groupId) { - if (is_numeric($groupId)) { - $friendIds[] = intval($groupId); - } else { - $manualGroupIds[] = $groupId; - } - } - - $wechatGroupsOptions = []; - - // 查询好友信息(数字ID) - if (!empty($friendIds)) { - $friendList = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->join(['s2_company_account' => 'ca'], 'ca.id = wf.accountId', 'left') - ->where('wf.id', 'in', $friendIds) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname,wf.avatar,wf.alias,wf.gender,wf.phone,wa.nickName as accountNickname,ca.userName as account,ca.realName as username,wf.createTime,wf.updateTime,wf.deleteTime,wf.ownerWechatId') - ->select(); - - // 获取群主信息 - foreach ($friendList as &$friend) { - if (!empty($friend['ownerWechatId'])) { - $owner = Db::name('wechat_account') - ->where('wechatId', $friend['ownerWechatId']) - ->field('nickName,alias') - ->find(); - $friend['ownerNickname'] = $owner['nickName'] ?? ''; - $friend['ownerAlias'] = $owner['alias'] ?? ''; - } else { - $friend['ownerNickname'] = ''; - $friend['ownerAlias'] = ''; - } - $friend['isManual'] = ''; - // 格式化时间 - $friend['createTime'] = !empty($friend['createTime']) ? date('Y-m-d H:i:s', $friend['createTime']) : ''; - $friend['updateTime'] = !empty($friend['updateTime']) ? date('Y-m-d H:i:s', $friend['updateTime']) : ''; - $friend['deleteTime'] = !empty($friend['deleteTime']) ? date('Y-m-d H:i:s', $friend['deleteTime']) : ''; - } - unset($friend); - - $wechatGroupsOptions = array_merge($wechatGroupsOptions, $friendList); - } - - // 处理手动创建的群组(字符串ID) - if (!empty($manualGroupIds)) { - foreach ($manualGroupIds as $groupId) { - // 手动创建的群组,只返回基本信息 - $wechatGroupsOptions[] = [ - 'id' => $groupId, - 'wechatId' => $groupId, - 'nickname' => $groupId, - 'avatar' => '', - 'isManual' => 1 - ]; - } - } - - $workbench->config->wechatGroupsOptions = $wechatGroupsOptions; - } else { - $workbench->config->wechatGroupsOptions = []; - } - - // 获取管理员选项(自动建群) - if ($workbench->type == self::TYPE_GROUP_CREATE && !empty($workbench->config->admins)) { - $adminOptions = Db::table('s2_wechat_friend')->alias('wf') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wf.wechatAccountId', 'left') - ->where('wf.id', 'in', $workbench->config->admins) - ->order('wf.id', 'desc') - ->field('wf.id,wf.wechatId,wf.nickname as friendName,wf.avatar as friendAvatar,wf.conRemark,wf.ownerWechatId,wa.nickName as accountName,wa.avatar as accountAvatar') - ->select(); - $workbench->config->adminsOptions = $adminOptions; - } else { - $workbench->config->adminsOptions = []; - } - - return json(['code' => 200, 'msg' => '获取成功', 'data' => $workbench]); - } - - /** - * 更新工作台 - * @return \think\response\Json - */ - public function update() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - // 获取请求参数 - $param = $this->request->post(); - - // 验证数据 - $validate = new WorkbenchValidate; - if (!$validate->scene('update')->check($param)) { - return json(['code' => 400, 'msg' => $validate->getError()]); - } - - - $where = [ - ['id', '=', $param['id']], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - // 查询工作台是否存在 - $workbench = Workbench::where($where)->find(); - if (!$workbench) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - Db::startTrans(); - try { - // 更新工作台基本信息 - $workbench->name = $param['name']; - $workbench->status = !empty($param['status']) ? 1 : 0; - $workbench->autoStart = !empty($param['autoStart']) ? 1 : 0; - $workbench->updateTime = time(); - $workbench->save(); - - // 根据类型更新对应的配置 - switch ($workbench->type) { - case self::TYPE_AUTO_LIKE: - $config = WorkbenchAutoLike::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->interval = $param['interval']; - $config->maxLikes = $param['maxLikes']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->contentTypes = json_encode($param['contentTypes']); - $config->devices = json_encode($param['deviceGroups']); - $config->friends = json_encode($param['wechatFriends']); - // $config->targetGroups = json_encode($param['targetGroups']); - // $config->tagOperator = $param['tagOperator']; - $config->friendMaxLikes = $param['friendMaxLikes']; - $config->friendTags = $param['friendTags']; - $config->enableFriendTags = $param['enableFriendTags']; - $config->updateTime = time(); - $config->save(); - } - break; - - case self::TYPE_MOMENTS_SYNC: - $config = WorkbenchMomentsSync::where('workbenchId', $param['id'])->find(); - if ($config) { - if (!empty($param['contentGroups'])) { - foreach ($param['contentGroups'] as $library) { - if (isset($library['id']) && !empty($library['id'])) { - $contentLibraries[] = $library['id']; - } else { - $contentLibraries[] = $library; - } - } - } else { - $contentLibraries = []; - } - - $config->syncInterval = $param['syncInterval']; - $config->syncCount = $param['syncCount']; - $config->syncType = $param['syncType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->accountType = $param['accountType']; - $config->devices = json_encode($param['deviceGroups']); - $config->contentLibraries = json_encode($contentLibraries); - $config->updateTime = time(); - $config->save(); - } - break; - - case self::TYPE_GROUP_PUSH: - $config = WorkbenchGroupPush::where('workbenchId', $param['id'])->find(); - if ($config) { - $ownerWechatIds = $this->normalizeOwnerWechatIds($param['ownerWechatIds'] ?? null, $config, $param['deviceGroups'] ?? []); - $groupPushData = $this->prepareGroupPushData($param, $ownerWechatIds, $config); - $groupPushData['updateTime'] = time(); - $config->save($groupPushData); - } - break; - - case self::TYPE_GROUP_CREATE: - $config = WorkbenchGroupCreate::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->devices = json_encode($param['deviceGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->startTime = $param['startTime'] ?? ''; - $config->endTime = $param['endTime'] ?? ''; - $config->groupSizeMin = intval($param['groupSizeMin'] ?? 3); - $config->groupSizeMax = intval($param['groupSizeMax'] ?? 38); - $config->maxGroupsPerDay = intval($param['maxGroupsPerDay'] ?? 20); - $config->groupNameTemplate = $param['groupNameTemplate'] ?? ''; - $config->groupDescription = $param['groupDescription'] ?? ''; - $config->poolGroups = json_encode($param['poolGroups'] ?? [], JSON_UNESCAPED_UNICODE); - $config->wechatGroups = json_encode($param['wechatGroups'] ?? [], JSON_UNESCAPED_UNICODE); - - // 处理群管理员:如果启用了群管理员且有指定管理员,则保存到admins字段 - $admins = []; - if (!empty($param['groupAdminEnabled']) && !empty($param['groupAdminWechatId'])) { - // 如果groupAdminWechatId是数组,取第一个;如果是单个值,直接使用 - $adminWechatId = is_array($param['groupAdminWechatId']) ? $param['groupAdminWechatId'][0] : $param['groupAdminWechatId']; - // 如果是好友ID,直接添加到admins;如果是wechatId,需要转换为好友ID - if (is_numeric($adminWechatId)) { - $admins[] = intval($adminWechatId); - } else { - // 如果是wechatId字符串,需要查询对应的好友ID - $friend = Db::table('s2_wechat_friend')->where('wechatId', $adminWechatId)->find(); - if ($friend) { - $admins[] = intval($friend['id']); - } - } - } - // 如果传入了admins参数,优先使用(兼容旧逻辑) - if (!empty($param['admins']) && is_array($param['admins'])) { - $admins = array_merge($admins, $param['admins']); - } - $config->admins = json_encode(array_unique($admins), JSON_UNESCAPED_UNICODE); - - $config->updateTime = time(); - $config->save(); - } - break; - case self::TYPE_TRAFFIC_DISTRIBUTION: - $config = WorkbenchTrafficConfig::where('workbenchId', $param['id'])->find(); - if ($config) { - $config->distributeType = $param['distributeType']; - $config->maxPerDay = $param['maxPerDay']; - $config->timeType = $param['timeType']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->devices = json_encode($param['deviceGroups']); - $config->pools = json_encode($param['poolGroups']); - $config->account = json_encode($param['accountGroups']); - $config->updateTime = time(); - $config->save(); - } - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = WorkbenchImportContact::where('workbenchId', $param['id'])->find();; - if ($config) { - $config->devices = json_encode($param['deviceGroups']); - $config->pools = json_encode($param['poolGroups']); - $config->num = $param['num']; - $config->clearContact = $param['clearContact']; - $config->remark = $param['remark']; - $config->startTime = $param['startTime']; - $config->endTime = $param['endTime']; - $config->save(); - } - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '更新成功']); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]); - } - } - - /** - * 更新工作台状态 - * @return \think\response\Json - */ - public function updateStatus() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - $id = $this->request->param('id', ''); - - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - $workbench->status = !$workbench['status']; - $workbench->save(); - - return json(['code' => 200, 'msg' => '更新成功']); - } - - /** - * 删除工作台(软删除) - */ - public function delete() - { - $id = $this->request->param('id'); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - $where = [ - ['id', '=', $id], - ['companyId', '=', $this->request->userInfo['companyId']], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - $workbench = Workbench::where($where)->find(); - - if (!$workbench) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - // 软删除 - $workbench->isDel = 1; - $workbench->deleteTime = time(); - $workbench->save(); - - return json(['code' => 200, 'msg' => '删除成功']); - } - - /** - * 拷贝工作台 - * @return \think\response\Json - */ - public function copy() - { - if (!$this->request->isPost()) { - return json(['code' => 400, 'msg' => '请求方式错误']); - } - - $id = $this->request->post('id'); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 验证权限并获取原数据 - $workbench = Workbench::where([ - ['id', '=', $id], - ['userId', '=', $this->request->userInfo['id']] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - - Db::startTrans(); - try { - // 创建新的工作台基本信息 - $newWorkbench = new Workbench; - $newWorkbench->name = $workbench->name . ' copy'; - $newWorkbench->type = $workbench->type; - $newWorkbench->status = 1; // 新拷贝的默认启用 - $newWorkbench->autoStart = $workbench->autoStart; - $newWorkbench->userId = $this->request->userInfo['id']; - $newWorkbench->companyId = $this->request->userInfo['companyId']; - $newWorkbench->save(); - - // 根据类型拷贝对应的配置 - switch ($workbench->type) { - case self::TYPE_AUTO_LIKE: - $config = WorkbenchAutoLike::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchAutoLike; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->interval = $config->interval; - $newConfig->maxLikes = $config->maxLikes; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->contentTypes = $config->contentTypes; - $newConfig->devices = $config->devices; - $newConfig->friends = $config->friends; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_MOMENTS_SYNC: - $config = WorkbenchMomentsSync::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchMomentsSync; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->syncInterval = $config->syncInterval; - $newConfig->syncCount = $config->syncCount; - $newConfig->syncType = $config->syncType; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->accountType = $config->accountType; - $newConfig->devices = $config->devices; - $newConfig->contentLibraries = $config->contentLibraries; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_GROUP_PUSH: - $config = WorkbenchGroupPush::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchGroupPush; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->pushType = $config->pushType; - $newConfig->targetType = isset($config->targetType) ? $config->targetType : 1; // 默认1=群推送 - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->maxPerDay = $config->maxPerDay; - $newConfig->pushOrder = $config->pushOrder; - $newConfig->isLoop = $config->isLoop; - $newConfig->status = $config->status; - $newConfig->groups = $config->groups; - $newConfig->friends = $config->friends; - $newConfig->contentLibraries = $config->contentLibraries; - $newConfig->trafficPools = property_exists($config, 'trafficPools') ? $config->trafficPools : json_encode([], JSON_UNESCAPED_UNICODE); - $newConfig->socialMediaId = $config->socialMediaId; - $newConfig->promotionSiteId = $config->promotionSiteId; - $newConfig->ownerWechatIds = $config->ownerWechatIds; - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_GROUP_CREATE: - $config = WorkbenchGroupCreate::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchGroupCreate; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->devices = $config->devices; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->groupSizeMin = $config->groupSizeMin; - $newConfig->groupSizeMax = $config->groupSizeMax; - $newConfig->maxGroupsPerDay = $config->maxGroupsPerDay; - $newConfig->groupNameTemplate = $config->groupNameTemplate; - $newConfig->groupDescription = $config->groupDescription; - $newConfig->poolGroups = $config->poolGroups; - $newConfig->wechatGroups = $config->wechatGroups; - $newConfig->admins = $config->admins ?? json_encode([], JSON_UNESCAPED_UNICODE); - $newConfig->createTime = time(); - $newConfig->updateTime = time(); - $newConfig->save(); - } - break; - case self::TYPE_IMPORT_CONTACT: //联系人导入 - $config = WorkbenchImportContact::where('workbenchId', $id)->find(); - if ($config) { - $newConfig = new WorkbenchImportContact; - $newConfig->workbenchId = $newWorkbench->id; - $newConfig->devices = $config->devices; - $newConfig->pools = $config->pools; - $newConfig->num = $config->num; - $newConfig->clearContact = $config->clearContact; - $newConfig->remark = $config->remark; - $newConfig->startTime = $config->startTime; - $newConfig->endTime = $config->endTime; - $newConfig->createTime = time(); - $newConfig->save(); - } - break; - } - - Db::commit(); - return json(['code' => 200, 'msg' => '拷贝成功', 'data' => ['id' => $newWorkbench->id]]); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '拷贝失败:' . $e->getMessage()]); - } - } - - /** - * 获取点赞记录列表 - * @return \think\response\Json - */ - public function getLikeRecords() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchAutoLikeController(); - $controller->request = $this->request; - return $controller->getLikeRecords(); - } - - /** - * 获取朋友圈发布记录列表 - * @return \think\response\Json - */ - public function getMomentsRecords() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController(); - $controller->request = $this->request; - return $controller->getMomentsRecords(); - } - - /** - * 获取朋友圈发布统计 - * @return \think\response\Json - */ - public function getMomentsStats() - { - $controller = new \app\cunkebao\controller\workbench\WorkbenchMomentsController(); - $controller->request = $this->request; - return $controller->getMomentsStats(); - } - - /** - * 获取流量分发记录列表 - * @return \think\response\Json - */ - public function getTrafficDistributionRecords() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $workbenchId = $this->request->param('workbenchId', 0); - - $where = [ - ['wtdi.workbenchId', '=', $workbenchId] - ]; - - // 查询分发记录 - $list = Db::name('workbench_traffic_distribution_item')->alias('wtdi') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left') - ->field([ - 'wtdi.id', - 'wtdi.workbenchId', - 'wtdi.wechatAccountId', - 'wtdi.wechatFriendId', - 'wtdi.createTime as distributeTime', - 'wtdi.status', - 'wtdi.errorMsg', - 'wa.nickName as operatorName', - 'wa.avatar as operatorAvatar', - 'wf.nickName as friendName', - 'wf.avatar as friendAvatar', - 'wf.gender', - 'wf.province', - 'wf.city' - ]) - ->where($where) - ->order('wtdi.createTime', 'desc') - ->page($page, $limit) - ->select(); - - // 处理数据 - foreach ($list as &$item) { - // 处理时间格式 - $item['distributeTime'] = date('Y-m-d H:i:s', $item['distributeTime']); - - // 处理性别 - $genderMap = [ - 0 => '未知', - 1 => '男', - 2 => '女' - ]; - $item['genderText'] = $genderMap[$item['gender']] ?? '未知'; - - // 处理状态文字 - $statusMap = [ - 0 => '待分发', - 1 => '分发成功', - 2 => '分发失败' - ]; - $item['statusText'] = $statusMap[$item['status']] ?? '未知状态'; - } - - // 获取总记录数 - $total = Db::name('workbench_traffic_distribution_item')->alias('wtdi') - ->where($where) - ->count(); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取流量分发统计 - * @return \think\response\Json - */ - public function getTrafficDistributionStats() - { - $workbenchId = $this->request->param('workbenchId', 0); - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - // 获取今日数据 - $todayStart = strtotime(date('Y-m-d') . ' 00:00:00'); - $todayEnd = strtotime(date('Y-m-d') . ' 23:59:59'); - - $todayStats = Db::name('workbench_traffic_distribution_item') - ->where([ - ['workbenchId', '=', $workbenchId], - ['createTime', 'between', [$todayStart, $todayEnd]] - ]) - ->field([ - 'COUNT(*) as total', - 'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success', - 'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed' - ]) - ->find(); - - // 获取总数据 - $totalStats = Db::name('workbench_traffic_distribution_item') - ->where('workbenchId', $workbenchId) - ->field([ - 'COUNT(*) as total', - 'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as success', - 'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as failed' - ]) - ->find(); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'today' => [ - 'total' => intval($todayStats['total']), - 'success' => intval($todayStats['success']), - 'failed' => intval($todayStats['failed']) - ], - 'total' => [ - 'total' => intval($totalStats['total']), - 'success' => intval($totalStats['success']), - 'failed' => intval($totalStats['failed']) - ] - ] - ]); - } - - /** - * 获取流量分发详情 - * @return \think\response\Json - */ - public function getTrafficDistributionDetail() - { - $id = $this->request->param('id', 0); - if (empty($id)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - $detail = Db::name('workbench_traffic_distribution_item')->alias('wtdi') - ->join(['s2_wechat_account' => 'wa'], 'wa.id = wtdi.wechatAccountId', 'left') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wtdi.wechatFriendId', 'left') - ->field([ - 'wtdi.id', - 'wtdi.workbenchId', - 'wtdi.wechatAccountId', - 'wtdi.wechatFriendId', - 'wtdi.createTime as distributeTime', - 'wtdi.status', - 'wtdi.errorMsg', - 'wa.nickName as operatorName', - 'wa.avatar as operatorAvatar', - 'wf.nickName as friendName', - 'wf.avatar as friendAvatar', - 'wf.gender', - 'wf.province', - 'wf.city', - 'wf.signature', - 'wf.remark' - ]) - ->where('wtdi.id', $id) - ->find(); - - if (empty($detail)) { - return json(['code' => 404, 'msg' => '记录不存在']); - } - - // 处理数据 - $detail['distributeTime'] = date('Y-m-d H:i:s', $detail['distributeTime']); - - // 处理性别 - $genderMap = [ - 0 => '未知', - 1 => '男', - 2 => '女' - ]; - $detail['genderText'] = $genderMap[$detail['gender']] ?? '未知'; - - // 处理状态文字 - $statusMap = [ - 0 => '待分发', - 1 => '分发成功', - 2 => '分发失败' - ]; - $detail['statusText'] = $statusMap[$detail['status']] ?? '未知状态'; - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => $detail - ]); - } - - /** - * 创建流量分发计划 - * @return \think\response\Json - */ - public function createTrafficPlan() - { - $param = $this->request->post(); - Db::startTrans(); - try { - // 1. 创建主表 - $planId = Db::name('ck_workbench')->insertGetId([ - 'name' => $param['name'], - 'type' => self::TYPE_TRAFFIC_DISTRIBUTION, - 'status' => 1, - 'autoStart' => $param['autoStart'] ?? 0, - 'userId' => $this->request->userInfo['id'], - 'companyId' => $this->request->userInfo['companyId'], - 'createTime' => time(), - 'updateTime' => time() - ]); - // 2. 创建扩展表 - Db::name('ck_workbench_traffic_config')->insert([ - 'workbenchId' => $planId, - 'distributeType' => $param['distributeType'], - 'maxPerDay' => $param['maxPerDay'], - 'timeType' => $param['timeType'], - 'startTime' => $param['startTime'], - 'endTime' => $param['endTime'], - 'targets' => json_encode($param['targets'], JSON_UNESCAPED_UNICODE), - 'pools' => json_encode($param['poolGroups'], JSON_UNESCAPED_UNICODE), - 'createTime' => time(), - 'updateTime' => time() - ]); - Db::commit(); - return json(['code' => 200, 'msg' => '创建成功']); - } catch (\Exception $e) { - Db::rollback(); - return json(['code' => 500, 'msg' => '创建失败:' . $e->getMessage()]); - } - } - - /** - * 获取所有微信好友标签及数量统计 - * @return \think\response\Json - */ - public function getDeviceLabels() - { - $deviceIds = $this->request->param('deviceIds', ''); - $companyId = $this->request->userInfo['companyId']; - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - - $where = [ - ['wc.companyId', '=', $companyId], - ]; - - if (!empty($deviceIds)) { - $deviceIds = explode(',', $deviceIds); - $where[] = ['dwl.deviceId', 'in', $deviceIds]; - } - - $wechatAccounts = Db::name('wechat_customer')->alias('wc') - ->join('device_wechat_login dwl', 'dwl.wechatId = wc.wechatId AND dwl.companyId = wc.companyId AND dwl.alive = 1') - ->join(['s2_wechat_account' => 'wa'], 'wa.wechatId = wc.wechatId') - ->where($where) - ->field('wa.id,wa.wechatId,wa.nickName,wa.labels') - ->select(); - $labels = []; - $wechatIds = []; - foreach ($wechatAccounts as $account) { - $labelArr = json_decode($account['labels'], true); - if (is_array($labelArr)) { - foreach ($labelArr as $label) { - if ($label !== '' && $label !== null) { - $labels[] = $label; - } - } - } - $wechatIds[] = $account['wechatId']; - } - // 去重(只保留一个) - $labels = array_values(array_unique($labels)); - $wechatIds = array_unique($wechatIds); - - // 搜索过滤 - if (!empty($keyword)) { - $labels = array_filter($labels, function ($label) use ($keyword) { - return mb_stripos($label, $keyword) !== false; - }); - $labels = array_values($labels); // 重新索引数组 - } - - - // 分页处理 - $labels2 = array_slice($labels, ($page - 1) * $limit, $limit); - - // 统计数量 - $newLabel = []; - foreach ($labels2 as $label) { - $friendCount = Db::table('s2_wechat_friend') - ->whereIn('ownerWechatId', $wechatIds) - ->where('labels', 'like', '%"' . $label . '"%') - ->count(); - $newLabel[] = [ - 'label' => $label, - 'count' => $friendCount - ]; - } - - // 返回结果 - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $newLabel, - 'total' => count($labels), - ] - ]); - } - - - /** - * 获取群列表 - * @return \think\response\Json - */ - public function getGroupList() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - - $where = [ - ['wg.deleteTime', '=', 0], - ['wg.companyId', '=', $this->request->userInfo['companyId']], - ]; - - if (!empty($keyword)) { - $where[] = ['wg.name', 'like', '%' . $keyword . '%']; - } - - $query = Db::name('wechat_group')->alias('wg') - ->join('wechat_account wa', 'wa.wechatId = wg.ownerWechatId') - ->where($where); - - $total = $query->count(); - $list = $query->order('wg.id', 'desc') - ->field('wg.id,wg.name as groupName,wg.ownerWechatId,wa.nickName,wg.createTime,wa.avatar,wa.alias,wg.avatar as groupAvatar') - ->page($page, $limit) - ->select(); - - // 优化:格式化时间,头像兜底 - $defaultGroupAvatar = ''; - $defaultAvatar = ''; - foreach ($list as &$item) { - $item['createTime'] = $item['createTime'] ? date('Y-m-d H:i:s', $item['createTime']) : ''; - $item['groupAvatar'] = $item['groupAvatar'] ?: $defaultGroupAvatar; - $item['avatar'] = $item['avatar'] ?: $defaultAvatar; - } - - return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]); - } - - /** - * 获取流量池列表 - * @return \think\response\Json - */ - public function getTrafficPoolList() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - $companyId = $this->request->userInfo['companyId']; - - $baseQuery = Db::name('traffic_source_package')->alias('tsp') - ->where('tsp.isDel', 0) - ->whereIn('tsp.companyId', [$companyId, 0]); - - if (!empty($keyword)) { - $baseQuery->whereLike('tsp.name', '%' . $keyword . '%'); - } - - $total = (clone $baseQuery)->count(); - - $list = $baseQuery - ->leftJoin('traffic_source_package_item tspi', 'tspi.packageId = tsp.id and tspi.isDel = 0') - ->field('tsp.id,tsp.name,tsp.description,tsp.pic,tsp.companyId,COUNT(tspi.id) as itemCount,max(tspi.createTime) as latestImportTime') - ->group('tsp.id') - ->order('tsp.id', 'desc') - ->page($page, $limit) - ->select(); - - foreach ($list as &$item) { - $item['latestImportTime'] = !empty($item['latestImportTime']) ? date('Y-m-d H:i:s', $item['latestImportTime']) : ''; - } - unset($item); - - return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]); - } - - - public function getAccountList() - { - $companyId = $this->request->userInfo['companyId']; - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $query = Db::table('s2_company_account') - ->alias('a') - ->where(['a.departmentId' => $companyId, 'a.status' => 0]) - ->whereNotLike('a.userName', '%_offline%') - ->whereNotLike('a.userName', '%_delete%'); - - $total = $query->count(); - $list = $query->field('a.id,a.userName,a.realName,a.nickname,a.memo') - ->page($page, $limit) - ->select(); - - - return json(['code' => 200, 'msg' => '获取成功', 'data' => ['total' => $total, 'list' => $list]]); - } - - - /** - * 获取京东联盟导购媒体 - * @return \think\response\Json - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\ModelNotFoundException - * @throws \think\exception\DbException - */ - public function getJdSocialMedia() - { - $data = Db::name('jd_social_media')->order('id DESC')->select(); - return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]); - } - - /** - * 获取京东联盟广告位 - * @return \think\response\Json - * @throws \think\db\exception\DataNotFoundException - * @throws \think\db\exception\ModelNotFoundException - * @throws \think\exception\DbException - */ - public function getJdPromotionSite() - { - $id = $this->request->param('id', ''); - if (empty($id)) { - return json(['code' => 500, 'msg' => '参数缺失']); - } - - $data = Db::name('jd_promotion_site')->where('jdSocialMediaId', $id)->order('id DESC')->select(); - return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]); - } - - - //京东转链-京推推 - public function changeLink($content = '', $positionid = '') - { - $unionId = Env::get('jd.unionId', ''); - $jttAppId = Env::get('jd.jttAppId', ''); - $appKey = Env::get('jd.appKey', ''); - $apiUrl = Env::get('jd.apiUrl', ''); - - $content = !empty($content) ? $content : $this->request->param('content', ''); - $positionid = !empty($positionid) ? $positionid : $this->request->param('positionid', ''); - - - if (empty($content)) { - return json_encode(['code' => 500, 'msg' => '转链的内容为空']); - } - - // 验证是否包含链接 - if (!$this->containsLink($content)) { - return json_encode(['code' => 500, 'msg' => '内容中未检测到有效链接']); - } - - if (empty($unionId) || empty($jttAppId) || empty($appKey) || empty($apiUrl)) { - return json_encode(['code' => 500, 'msg' => '参数缺失']); - } - $params = [ - 'unionid' => $unionId, - 'content' => $content, - 'appid' => $jttAppId, - 'appkey' => $appKey, - 'v' => 'v2' - ]; - - if (!empty($positionid)) { - $params['positionid'] = $positionid; - } - - $res = requestCurl($apiUrl, $params, 'GET', [], 'json'); - $res = json_decode($res, true); - if (empty($res)) { - return json_encode(['code' => 500, 'msg' => '未知错误']); - } - $result = $res['result']; - if ($res['return'] == 0) { - return json_encode(['code' => 200, 'data' => $result['chain_content'], 'msg' => $result['msg']]); - } else { - return json_encode(['code' => 500, 'msg' => $result['msg']]); - } - } - - - public function getTrafficList() - { - $companyId = $this->request->userInfo['companyId']; - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $keyword = $this->request->param('keyword', ''); - $workbenchId = $this->request->param('workbenchId', ''); - $isRecycle = $this->request->param('isRecycle', ''); - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '参数错误']); - } - - $workbench = Db::name('workbench')->where(['id' => $workbenchId, 'isDel' => 0, 'companyId' => $companyId, 'type' => 5])->find(); - - if (empty($workbench)) { - return json(['code' => 400, 'msg' => '该任务不存在或已删除']); - } - $query = Db::name('workbench_traffic_config_item')->alias('wtc') - ->join(['s2_wechat_friend' => 'wf'], 'wtc.wechatFriendId = wf.id') - ->join('users u', 'wtc.wechatAccountId = u.s2_accountId', 'left') - ->field([ - 'wtc.id', 'wtc.isRecycle', 'wtc.isRecycle', 'wtc.createTime','wtc.recycleTime', - 'wf.wechatId', 'wf.alias', 'wf.nickname', 'wf.avatar', 'wf.gender', 'wf.phone', - 'u.account', 'u.username' - ]) - ->where(['wtc.workbenchId' => $workbenchId]) - ->order('wtc.id DESC'); - - if (!empty($keyword)) { - $query->where('wf.wechatId|wf.alias|wf.nickname|wf.phone|u.account|u.username', 'like', '%' . $keyword . '%'); - } - - if ($isRecycle != '' || $isRecycle != null) { - $query->where('isRecycle',$isRecycle); - } - - - - $total = $query->count(); - $list = $query->page($page, $limit)->select(); - - foreach ($list as &$item) { - $item['createTime'] = date('Y-m-d H:i:s', $item['createTime']); - $item['recycleTime'] = date('Y-m-d H:i:s', $item['recycleTime']); - } - unset($item); - - - $data = [ - 'total' => $total, - 'list' => $list, - ]; - - return json(['code' => 200, 'msg' => '获取成功', 'data' => $data]); - - } - - /** - * 规范化客服微信ID列表 - * @param mixed $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @param array $deviceGroups 设备ID数组 - * @return array - * @throws \Exception - */ - private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null, array $deviceGroups = []): array - { - // 处理设备ID数组 - $deviceGroupsList = $this->extractIdList($deviceGroups, '设备参数格式错误'); - - if ($ownerWechatIds === null) { - $existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : []; - // 如果原有配置为空,且没有传设备ID,则报错 - if (empty($existing) && empty($deviceGroupsList)) { - throw new \Exception('请至少选择一个客服微信或设备'); - } - // 如果原有配置为空但有设备ID,返回空数组(允许使用设备ID) - if (empty($existing) && !empty($deviceGroupsList)) { - return []; - } - return $existing; - } - - if (!is_array($ownerWechatIds)) { - throw new \Exception('客服参数格式错误'); - } - - $normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误'); - // 如果 ownerWechatIds 为空,但传了设备ID,则允许(两个传一个即可) - if (empty($normalized) && empty($deviceGroupsList)) { - throw new \Exception('请至少选择一个客服微信或设备'); - } - return $normalized; - } - - /** - * 构建群推送配置数据 - * @param array $param - * @param array $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @return array - * @throws \Exception - */ - private function prepareGroupPushData(array $param, array $ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array - { - $targetTypeDefault = $originalConfig ? intval($originalConfig->targetType) : 1; - $targetType = intval($this->getParamValue($param, 'targetType', $targetTypeDefault)) ?: 1; - - $groupPushSubTypeDefault = $originalConfig ? intval($originalConfig->groupPushSubType) : 1; - $groupPushSubType = intval($this->getParamValue($param, 'groupPushSubType', $groupPushSubTypeDefault)) ?: 1; - if (!in_array($groupPushSubType, [1, 2], true)) { - $groupPushSubType = 1; - } - - $data = [ - 'pushType' => $this->toBoolInt($this->getParamValue($param, 'pushType', $originalConfig->pushType ?? 0)), - 'targetType' => $targetType, - 'startTime' => $this->getParamValue($param, 'startTime', $originalConfig->startTime ?? ''), - 'endTime' => $this->getParamValue($param, 'endTime', $originalConfig->endTime ?? ''), - 'maxPerDay' => intval($this->getParamValue($param, 'maxPerDay', $originalConfig->maxPerDay ?? 0)), - 'pushOrder' => $this->getParamValue($param, 'pushOrder', $originalConfig->pushOrder ?? 1), - 'groupPushSubType' => $groupPushSubType, - 'status' => $this->toBoolInt($this->getParamValue($param, 'status', $originalConfig->status ?? 0)), - 'socialMediaId' => $this->getParamValue($param, 'socialMediaId', $originalConfig->socialMediaId ?? ''), - 'promotionSiteId' => $this->getParamValue($param, 'promotionSiteId', $originalConfig->promotionSiteId ?? ''), - 'friendIntervalMin' => intval($this->getParamValue($param, 'friendIntervalMin', $originalConfig->friendIntervalMin ?? 10)), - 'friendIntervalMax' => intval($this->getParamValue($param, 'friendIntervalMax', $originalConfig->friendIntervalMax ?? 20)), - 'messageIntervalMin' => intval($this->getParamValue($param, 'messageIntervalMin', $originalConfig->messageIntervalMin ?? 1)), - 'messageIntervalMax' => intval($this->getParamValue($param, 'messageIntervalMax', $originalConfig->messageIntervalMax ?? 12)), - 'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)), - 'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE), - ]; - - // 处理设备ID数组(deviceGroups),保存到 devices 字段 - $deviceGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->devices ?? []) : []; - $deviceGroupsParam = $this->getParamValue($param, 'deviceGroups', null); - $deviceGroups = $deviceGroupsParam !== null - ? $this->extractIdList($deviceGroupsParam, '设备参数格式错误') - : $deviceGroupsExisting; - $data['devices'] = json_encode($deviceGroups, JSON_UNESCAPED_UNICODE); - - if ($data['friendIntervalMin'] > $data['friendIntervalMax']) { - throw new \Exception('目标间最小间隔不能大于最大间隔'); - } - if ($data['messageIntervalMin'] > $data['messageIntervalMax']) { - throw new \Exception('消息间最小间隔不能大于最大间隔'); - } - - // 群公告(groupPushSubType = 2)时,contentGroups 可以为空,不需要验证 - if ($targetType === 1 && $groupPushSubType === 2) { - // 群公告可以不传内容库,允许为空,不进行任何验证 - $contentGroupsParam = $this->getParamValue($param, 'contentGroups', null); - if ($contentGroupsParam !== null) { - // 如果传入了参数,尝试解析,但不验证格式和是否为空 - try { - $contentGroups = $this->extractIdList($contentGroupsParam, '内容库参数格式错误'); - } catch (\Exception $e) { - // 群公告时忽略格式错误,使用空数组 - $contentGroups = []; - } - } else { - // 如果没有传入参数,使用现有配置或空数组 - $contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : []; - $contentGroups = $contentGroupsExisting; - } - } else { - // 其他情况,正常处理并验证 - $contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : []; - $contentGroupsParam = $this->getParamValue($param, 'contentGroups', null); - $contentGroups = $contentGroupsParam !== null - ? $this->extractIdList($contentGroupsParam, '内容库参数格式错误') - : $contentGroupsExisting; - // 其他情况,内容库为必填 - if (empty($contentGroups)) { - throw new \Exception('请至少选择一个内容库'); - } - } - $data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE); - - $postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : []; - $postPushTagsParam = $this->getParamValue($param, 'postPushTags', null); - $postPushTags = $postPushTagsParam !== null - ? $this->extractIdList($postPushTagsParam, '推送标签参数格式错误') - : $postPushTagsExisting; - $data['postPushTags'] = json_encode($postPushTags, JSON_UNESCAPED_UNICODE); - - if ($targetType === 1) { - $data['isLoop'] = $this->toBoolInt($this->getParamValue($param, 'isLoop', $originalConfig->isLoop ?? 0)); - - $groupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->groups ?? []) : []; - $wechatGroups = array_key_exists('wechatGroups', $param) - ? $this->extractIdList($param['wechatGroups'], '群参数格式错误') - : $groupsExisting; - if (empty($wechatGroups)) { - throw new \Exception('群推送必须选择微信群'); - } - $data['groups'] = json_encode($wechatGroups, JSON_UNESCAPED_UNICODE); - $data['friends'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode([], JSON_UNESCAPED_UNICODE); - - if ($groupPushSubType === 2) { - $announcementContent = $this->getParamValue($param, 'announcementContent', $originalConfig->announcementContent ?? ''); - if (empty($announcementContent)) { - throw new \Exception('群公告必须输入公告内容'); - } - $enableAiRewrite = $this->toBoolInt($this->getParamValue($param, 'enableAiRewrite', $originalConfig->enableAiRewrite ?? 0)); - $aiRewritePrompt = trim((string)$this->getParamValue($param, 'aiRewritePrompt', $originalConfig->aiRewritePrompt ?? '')); - if ($enableAiRewrite === 1 && $aiRewritePrompt === '') { - throw new \Exception('启用AI智能话术改写时,必须输入改写提示词'); - } - $data['announcementContent'] = $announcementContent; - $data['enableAiRewrite'] = $enableAiRewrite; - $data['aiRewritePrompt'] = $aiRewritePrompt; - } else { - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - } else { - $data['isLoop'] = 0; - $friendsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->friends ?? []) : []; - $trafficPoolsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->trafficPools ?? []) : []; - - $friendTargets = array_key_exists('wechatFriends', $param) - ? $this->extractIdList($param['wechatFriends'], '好友参数格式错误') - : $friendsExisting; - $trafficPools = array_key_exists('trafficPools', $param) - ? $this->extractIdList($param['trafficPools'], '流量池参数格式错误') - : $trafficPoolsExisting; - - if (empty($friendTargets) && empty($trafficPools)) { - throw new \Exception('好友推送需至少选择好友或流量池'); - } - - $data['friends'] = json_encode($friendTargets, JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode($trafficPools, JSON_UNESCAPED_UNICODE); - $data['groups'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - - return $data; - } - - /** - * 获取参数值,若不存在则返回默认值 - * @param array $param - * @param string $key - * @param mixed $default - * @return mixed - */ - private function getParamValue(array $param, string $key, $default) - { - return array_key_exists($key, $param) ? $param[$key] : $default; - } - - /** - * 将值转换为整型布尔 - * @param mixed $value - * @return int - */ - private function toBoolInt($value): int - { - return empty($value) ? 0 : 1; - } - - /** - * 从参数中提取ID列表 - * @param mixed $items - * @param string $errorMessage - * @return array - * @throws \Exception - */ - private function extractIdList($items, string $errorMessage = '参数格式错误'): array - { - if (!is_array($items)) { - throw new \Exception($errorMessage); - } - - $ids = []; - foreach ($items as $item) { - if (is_array($item) && isset($item['id'])) { - $item = $item['id']; - } - if ($item === '' || $item === null) { - continue; - } - $ids[] = $item; - } - - return array_values(array_unique($ids)); - } - - /** - * 解码JSON数组 - * @param mixed $value - * @return array - */ - private function decodeJsonArray($value): array - { - if (empty($value)) { - return []; - } - if (is_array($value)) { - return $value; - } - $decoded = json_decode($value, true); - return is_array($decoded) ? $decoded : []; - } - - /** - * 验证内容是否包含链接 - * @param string $content 要检测的内容 - * @return bool - */ - private function containsLink($content) - { - // 定义各种链接的正则表达式模式 - $patterns = [ - // HTTP/HTTPS链接 - '/https?:\/\/[^\s]+/i', - // 京东商品链接 - '/item\.jd\.com\/\d+/i', - // 京东短链接 - '/u\.jd\.com\/[a-zA-Z0-9]+/i', - // 淘宝商品链接 - '/item\.taobao\.com\/item\.htm\?id=\d+/i', - // 天猫商品链接 - '/detail\.tmall\.com\/item\.htm\?id=\d+/i', - // 淘宝短链接 - '/m\.tb\.cn\/[a-zA-Z0-9]+/i', - // 拼多多链接 - '/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i', - // 苏宁易购链接 - '/product\.suning\.com\/\d+\/\d+\.html/i', - // 通用域名模式(包含常见电商域名) - '/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i', - // 通用短链接模式 - '/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i' - ]; - - // 遍历所有模式进行匹配 - foreach ($patterns as $pattern) { - if (preg_match($pattern, $content)) { - return true; - } - } - - return false; - } - - /** - * 获取群发统计数据 - * @return \think\response\Json - */ - public function getGroupPushStats() - { - $workbenchId = $this->request->param('workbenchId', 0); - $timeRange = $this->request->param('timeRange', '7'); // 默认最近7天 - $contentLibraryIds = $this->request->param('contentLibraryIds', ''); // 话术组筛选 - $userId = $this->request->userInfo['id']; - - // 如果指定了工作台ID,则验证权限 - if (!empty($workbenchId)) { - $workbench = Workbench::where([ - ['id', '=', $workbenchId], - ['userId', '=', $userId], - ['type', '=', self::TYPE_GROUP_PUSH], - ['isDel', '=', 0] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - } - - // 计算时间范围 - $days = intval($timeRange); - $startTime = strtotime(date('Y-m-d 00:00:00', strtotime("-{$days} days"))); - $endTime = time(); - - // 构建查询条件 - $where = [ - ['wgpi.createTime', '>=', $startTime], - ['wgpi.createTime', '<=', $endTime] - ]; - - // 如果指定了工作台ID,则限制查询范围 - if (!empty($workbenchId)) { - $where[] = ['wgpi.workbenchId', '=', $workbenchId]; - } else { - // 如果没有指定工作台ID,则查询当前用户的所有群推送工作台 - $workbenchIds = Workbench::where([ - ['userId', '=', $userId], - ['type', '=', self::TYPE_GROUP_PUSH], - ['isDel', '=', 0] - ])->column('id'); - - if (empty($workbenchIds)) { - // 如果没有工作台,返回空结果 - $workbenchIds = [-1]; - } - $where[] = ['wgpi.workbenchId', 'in', $workbenchIds]; - } - - // 话术组筛选 - 先获取符合条件的内容ID列表 - $contentIds = null; - if (!empty($contentLibraryIds)) { - $libraryIds = is_array($contentLibraryIds) ? $contentLibraryIds : explode(',', $contentLibraryIds); - $libraryIds = array_filter(array_map('intval', $libraryIds)); - if (!empty($libraryIds)) { - // 查询符合条件的内容ID - $contentIds = Db::name('content_item') - ->whereIn('libraryId', $libraryIds) - ->column('id'); - if (empty($contentIds)) { - // 如果没有符合条件的内容,返回空结果 - $contentIds = [-1]; // 使用不存在的ID,确保查询结果为空 - } - } - } - - // 1. 基础统计:触达率、回复率、平均回复时间、链接点击率 - $stats = $this->calculateBasicStats($workbenchId, $where, $startTime, $endTime, $contentIds); - - // 2. 话术组对比 - $contentLibraryComparison = $this->getContentLibraryComparison($workbenchId, $where, $startTime, $endTime, $contentIds); - - // 3. 时段分析 - $timePeriodAnalysis = $this->getTimePeriodAnalysis($workbenchId, $where, $startTime, $endTime, $contentIds); - - // 4. 互动深度(可选,需要更多数据) - $interactionDepth = $this->getInteractionDepth($workbenchId, $where, $startTime, $endTime, $contentIds); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'basicStats' => $stats, - 'contentLibraryComparison' => $contentLibraryComparison, - 'timePeriodAnalysis' => $timePeriodAnalysis, - 'interactionDepth' => $interactionDepth - ] - ]); - } - - /** - * 计算基础统计数据 - */ - private function calculateBasicStats($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - // 获取工作台配置,计算计划发送数 - // 如果 workbenchId 为空,则查询所有工作台的配置 - $configQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - - if (!empty($workbenchId)) { - $configQuery->where('wgp.workbenchId', $workbenchId); - } else { - // 如果没有指定工作台ID,需要从 where 条件中获取 workbenchId 列表 - $workbenchIdCondition = null; - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - if ($condition[1] === 'in' && is_array($condition[2])) { - $workbenchIdCondition = $condition[2]; - break; - } elseif ($condition[1] === '=') { - $workbenchIdCondition = [$condition[2]]; - break; - } - } - } - if ($workbenchIdCondition) { - $configQuery->whereIn('wgp.workbenchId', $workbenchIdCondition); - } - } - - $configs = $configQuery->select(); - $targetType = 1; // 默认值 - if (!empty($configs)) { - // 如果只有一个配置,使用它的 targetType;如果有多个,默认使用1 - $targetType = intval($configs[0]->targetType ?? 1); - } - - // 计划发送数(根据配置计算) - $plannedSend = 0; - if (!empty($configs)) { - $days = ceil(($endTime - $startTime) / 86400); - foreach ($configs as $config) { - $maxPerDay = intval($config->maxPerDay ?? 0); - $configTargetType = intval($config->targetType ?? 1); - if ($configTargetType == 1) { - // 群推送:计划发送数 = 每日推送次数 * 天数 * 群数量 - $groups = $this->decodeJsonArray($config->groups ?? []); - $plannedSend += $maxPerDay * $days * count($groups); - } else { - // 好友推送:计划发送数 = 每日推送人数 * 天数 - $plannedSend += $maxPerDay * $days; - } - } - } - - // 构建查询条件 - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - // 实际成功发送数(从推送记录表统计) - $successSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->count(); - - // 触达率 = 成功发送数 / 计划发送数 - $reachRate = $plannedSend > 0 ? round(($successSend / $plannedSend) * 100, 1) : 0; - - // 获取发送记录列表,用于查询回复 - $sentItemIds = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - - // 回复统计(通过消息表查询) - $replyStats = $this->calculateReplyStats($sentItemIds, $targetType, $startTime, $endTime); - - // 链接点击统计 - $clickStats = $this->calculateClickStats($sentItemIds, $targetType, $startTime, $endTime); - - // 计算本月对比数据(简化处理,实际应该查询上个月同期数据) - $currentMonthStart = strtotime(date('Y-m-01 00:00:00')); - $lastMonthStart = strtotime(date('Y-m-01 00:00:00', strtotime('-1 month'))); - $lastMonthEnd = $currentMonthStart - 1; - - // 获取本月统计数据(避免递归调用) - $currentMonthWhere = [ - ['wgpi.createTime', '>=', $currentMonthStart] - ]; - // 复制 workbenchId 条件 - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - $currentMonthWhere[] = $condition; - break; - } - } - if ($contentIds !== null) { - $currentMonthWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - $currentMonthSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($currentMonthWhere) - ->count(); - - // 获取本月配置 - $currentMonthConfigQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - if (!empty($workbenchId)) { - $currentMonthConfigQuery->where('wgp.workbenchId', $workbenchId); - } else { - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') { - $currentMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]); - break; - } - } - } - $currentMonthConfigs = $currentMonthConfigQuery->select(); - $currentMonthPlanned = 0; - if (!empty($currentMonthConfigs)) { - $currentMonthDays = ceil(($endTime - $currentMonthStart) / 86400); - foreach ($currentMonthConfigs as $currentMonthConfig) { - $currentMonthMaxPerDay = intval($currentMonthConfig->maxPerDay ?? 0); - $currentMonthTargetType = intval($currentMonthConfig->targetType ?? 1); - if ($currentMonthTargetType == 1) { - $currentMonthGroups = $this->decodeJsonArray($currentMonthConfig->groups ?? []); - $currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays * count($currentMonthGroups); - } else { - $currentMonthPlanned += $currentMonthMaxPerDay * $currentMonthDays; - } - } - } - $currentMonthReachRate = $currentMonthPlanned > 0 ? round(($currentMonthSend / $currentMonthPlanned) * 100, 1) : 0; - - // 获取上个月统计数据 - $lastMonthWhere = [ - ['wgpi.createTime', '>=', $lastMonthStart], - ['wgpi.createTime', '<=', $lastMonthEnd] - ]; - // 复制 workbenchId 条件 - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId') { - $lastMonthWhere[] = $condition; - break; - } - } - if ($contentIds !== null) { - $lastMonthWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - $lastMonthSend = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($lastMonthWhere) - ->count(); - - // 获取上个月配置 - $lastMonthConfigQuery = WorkbenchGroupPush::alias('wgp') - ->join('workbench w', 'w.id = wgp.workbenchId', 'left') - ->where('w.type', self::TYPE_GROUP_PUSH) - ->where('w.isDel', 0); - if (!empty($workbenchId)) { - $lastMonthConfigQuery->where('wgp.workbenchId', $workbenchId); - } else { - foreach ($where as $condition) { - if (is_array($condition) && isset($condition[0]) && $condition[0] === 'wgpi.workbenchId' && $condition[1] === 'in') { - $lastMonthConfigQuery->whereIn('wgp.workbenchId', $condition[2]); - break; - } - } - } - $lastMonthConfigs = $lastMonthConfigQuery->select(); - - $lastMonthPlanned = 0; - if (!empty($lastMonthConfigs)) { - $lastMonthDays = ceil(($lastMonthEnd - $lastMonthStart) / 86400); - foreach ($lastMonthConfigs as $lastMonthConfig) { - $lastMonthMaxPerDay = intval($lastMonthConfig->maxPerDay ?? 0); - $lastMonthTargetType = intval($lastMonthConfig->targetType ?? 1); - if ($lastMonthTargetType == 1) { - $lastMonthGroups = $this->decodeJsonArray($lastMonthConfig->groups ?? []); - $lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays * count($lastMonthGroups); - } else { - $lastMonthPlanned += $lastMonthMaxPerDay * $lastMonthDays; - } - } - } - $lastMonthReachRate = $lastMonthPlanned > 0 ? round(($lastMonthSend / $lastMonthPlanned) * 100, 1) : 0; - - // 获取上个月的回复和点击统计(简化处理) - $lastMonthSentItems = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($lastMonthWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - $lastMonthReplyStats = $this->calculateReplyStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd); - $lastMonthClickStats = $this->calculateClickStats($lastMonthSentItems, $targetType, $lastMonthStart, $lastMonthEnd); - - return [ - 'reachRate' => [ - 'value' => $reachRate, - 'trend' => round($reachRate - $lastMonthReachRate, 1), - 'unit' => '%', - 'description' => '成功发送/计划发送' - ], - 'replyRate' => [ - 'value' => $replyStats['replyRate'], - 'trend' => round($replyStats['replyRate'] - $lastMonthReplyStats['replyRate'], 1), - 'unit' => '%', - 'description' => '收到回复/成功发送' - ], - 'avgReplyTime' => [ - 'value' => $replyStats['avgReplyTime'], - 'trend' => round($lastMonthReplyStats['avgReplyTime'] - $replyStats['avgReplyTime'], 0), - 'unit' => '分钟', - 'description' => '从发送到回复的平均时长' - ], - 'clickRate' => [ - 'value' => $clickStats['clickRate'], - 'trend' => round($clickStats['clickRate'] - $lastMonthClickStats['clickRate'], 1), - 'unit' => '%', - 'description' => '点击链接/成功发送' - ], - 'plannedSend' => $plannedSend, - 'successSend' => $successSend, - 'replyCount' => $replyStats['replyCount'], - 'clickCount' => $clickStats['clickCount'] - ]; - } - - /** - * 计算回复统计 - */ - private function calculateReplyStats($sentItems, $targetType, $startTime, $endTime) - { - if (empty($sentItems)) { - return ['replyRate' => 0, 'avgReplyTime' => 0, 'replyCount' => 0]; - } - - $replyCount = 0; - $totalReplyTime = 0; - $replyTimes = []; - - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $sendTime = $itemArray['createTime'] ?? 0; - $accountId = $itemArray['wechatAccountId'] ?? 0; - - if ($targetType == 1) { - // 群推送:查找群内回复消息 - $groupId = $itemArray['groupId'] ?? 0; - $group = Db::name('wechat_group')->where('id', $groupId)->find(); - if ($group) { - $replyMsg = Db::table('s2_wechat_message') - ->where('wechatChatroomId', $group['chatroomId']) - ->where('wechatAccountId', $accountId) - ->where('isSend', 0) // 接收的消息 - ->where('wechatTime', '>', $sendTime) - ->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复 - ->order('wechatTime', 'asc') - ->find(); - - if ($replyMsg) { - $replyCount++; - $replyTime = $replyMsg['wechatTime'] - $sendTime; - $replyTimes[] = $replyTime; - $totalReplyTime += $replyTime; - } - } - } else { - // 好友推送:查找好友回复消息 - $friendId = $itemArray['friendId'] ?? 0; - $friend = Db::table('s2_wechat_friend')->where('id', $friendId)->find(); - if ($friend) { - $replyMsg = Db::table('s2_wechat_message') - ->where('wechatFriendId', $friendId) - ->where('wechatAccountId', $accountId) - ->where('isSend', 0) // 接收的消息 - ->where('wechatTime', '>', $sendTime) - ->where('wechatTime', '<=', $sendTime + 86400) // 24小时内回复 - ->order('wechatTime', 'asc') - ->find(); - - if ($replyMsg) { - $replyCount++; - $replyTime = $replyMsg['wechatTime'] - $sendTime; - $replyTimes[] = $replyTime; - $totalReplyTime += $replyTime; - } - } - } - } - - $successSend = count($sentItems); - $replyRate = $successSend > 0 ? round(($replyCount / $successSend) * 100, 1) : 0; - $avgReplyTime = $replyCount > 0 ? round(($totalReplyTime / $replyCount) / 60, 0) : 0; // 转换为分钟 - - return [ - 'replyRate' => $replyRate, - 'avgReplyTime' => $avgReplyTime, - 'replyCount' => $replyCount - ]; - } - - /** - * 计算链接点击统计 - */ - private function calculateClickStats($sentItems, $targetType, $startTime, $endTime) - { - if (empty($sentItems)) { - return ['clickRate' => 0, 'clickCount' => 0]; - } - - $clickCount = 0; - $linkContentIds = []; - - // 获取所有发送的内容ID - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $contentId = $itemArray['contentId'] ?? 0; - if ($contentId > 0) { - $linkContentIds[] = $contentId; - } - } - - if (empty($linkContentIds)) { - return ['clickRate' => 0, 'clickCount' => 0]; - } - - // 查询包含链接的内容 - $linkContents = Db::name('content_item') - ->whereIn('id', array_unique($linkContentIds)) - ->where('contentType', 2) // 链接类型 - ->column('id'); - - // 统计发送了链接内容的记录数 - $linkSendCount = 0; - foreach ($sentItems as $item) { - $itemArray = is_array($item) ? $item : (array)$item; - $contentId = $itemArray['contentId'] ?? 0; - if (in_array($contentId, $linkContents)) { - $linkSendCount++; - } - } - - // 简化处理:假设点击率基于链接消息的发送(实际应该从点击追踪系统获取) - // 这里可以根据业务需求调整,比如通过消息中的链接点击事件统计 - $clickCount = $linkSendCount; // 简化处理,实际需要真实的点击数据 - - $successSend = count($sentItems); - $clickRate = $successSend > 0 ? round(($clickCount / $successSend) * 100, 1) : 0; - - return [ - 'clickRate' => $clickRate, - 'clickCount' => $clickCount - ]; - } - - /** - * 获取话术组对比数据 - */ - private function getContentLibraryComparison($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - $comparison = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('content_item ci', 'ci.id = wgpi.contentId', 'left') - ->join('content_library cl', 'cl.id = ci.libraryId', 'left') - ->where($queryWhere) - ->where('cl.id', '<>', null) - ->field([ - 'cl.id as libraryId', - 'cl.name as libraryName', - 'COUNT(DISTINCT wgpi.id) as pushCount' - ]) - ->group('cl.id, cl.name') - ->select(); - - $result = []; - foreach ($comparison as $item) { - $libraryId = $item['libraryId']; - $pushCount = intval($item['pushCount']); - - // 获取该内容库的详细统计 - $libraryContentIds = Db::name('content_item') - ->where('libraryId', $libraryId) - ->column('id'); - if (empty($libraryContentIds)) { - $libraryContentIds = [-1]; - } - - $libraryWhere = array_merge($where, [['wgpi.contentId', 'in', $libraryContentIds]]); - $librarySentItems = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($libraryWhere) - ->field('wgpi.id, wgpi.groupId, wgpi.friendId, wgpi.wechatAccountId, wgpi.createTime, wgpi.targetType, wgpi.contentId') - ->select(); - - $config = WorkbenchGroupPush::where('workbenchId', $workbenchId)->find(); - $targetType = $config ? intval($config->targetType) : 1; - - $replyStats = $this->calculateReplyStats($librarySentItems, $targetType, $startTime, $endTime); - $clickStats = $this->calculateClickStats($librarySentItems, $targetType, $startTime, $endTime); - - // 计算转化率(简化处理,实际需要根据业务定义) - $conversionRate = $pushCount > 0 ? round(($replyStats['replyCount'] / $pushCount) * 100, 1) : 0; - - $result[] = [ - 'libraryId' => $libraryId, - 'libraryName' => $item['libraryName'], - 'pushCount' => $pushCount, - 'reachRate' => 100, // 简化处理,实际应该计算 - 'replyRate' => $replyStats['replyRate'], - 'clickRate' => $clickStats['clickRate'], - 'conversionRate' => $conversionRate, - 'avgReplyTime' => $replyStats['avgReplyTime'], - 'level' => $this->getPerformanceLevel($replyStats['replyRate'], $clickStats['clickRate'], $conversionRate) - ]; - } - - // 按回复率排序 - usort($result, function($a, $b) { - return $b['replyRate'] <=> $a['replyRate']; - }); - - return $result; - } - - /** - * 获取性能等级 - */ - private function getPerformanceLevel($replyRate, $clickRate, $conversionRate) - { - $score = ($replyRate * 0.4) + ($clickRate * 0.3) + ($conversionRate * 0.3); - - if ($score >= 40) { - return '优秀'; - } elseif ($score >= 25) { - return '良好'; - } elseif ($score >= 15) { - return '一般'; - } else { - return '待提升'; - } - } - - /** - * 获取时段分析数据 - */ - private function getTimePeriodAnalysis($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - $queryWhere = $where; - if ($contentIds !== null) { - $queryWhere[] = ['wgpi.contentId', 'in', $contentIds]; - } - - $analysis = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($queryWhere) - ->field([ - 'FROM_UNIXTIME(wgpi.createTime, "%H") as hour', - 'COUNT(*) as count' - ]) - ->group('hour') - ->order('hour', 'asc') - ->select(); - - $result = []; - foreach ($analysis as $item) { - $result[] = [ - 'hour' => intval($item['hour']), - 'count' => intval($item['count']) - ]; - } - - return $result; - } - - /** - * 获取互动深度数据 - */ - private function getInteractionDepth($workbenchId, $where, $startTime, $endTime, $contentIds = null) - { - // 简化处理,实际需要更复杂的统计逻辑 - return [ - 'singleReply' => 0, // 单次回复 - 'multipleReply' => 0, // 多次回复 - 'deepInteraction' => 0 // 深度互动 - ]; - } - - /** - * 获取推送历史记录列表 - * @return \think\response\Json - */ - public function getGroupPushHistory() - { - $page = $this->request->param('page', 1); - $limit = $this->request->param('limit', 10); - $workbenchId = $this->request->param('workbenchId', 0); - $keyword = $this->request->param('keyword', ''); - $pushType = $this->request->param('pushType', ''); // 推送类型筛选:''=全部, 'friend'=好友消息, 'group'=群消息, 'announcement'=群公告 - $status = $this->request->param('status', ''); // 状态筛选:''=全部, 'success'=已完成, 'progress'=进行中, 'failed'=失败 - $userId = $this->request->userInfo['id']; - - // 构建工作台查询条件 - $workbenchWhere = [ - ['w.userId', '=', $userId], - ['w.type', '=', self::TYPE_GROUP_PUSH], - ['w.isDel', '=', 0] - ]; - - // 如果指定了工作台ID,则验证权限并限制查询范围 - if (!empty($workbenchId)) { - $workbench = Workbench::where([ - ['id', '=', $workbenchId], - ['userId', '=', $userId], - ['type', '=', self::TYPE_GROUP_PUSH], - ['isDel', '=', 0] - ])->find(); - - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在']); - } - $workbenchWhere[] = ['w.id', '=', $workbenchId]; - } - - // 1. 先查询所有已执行的推送记录(按推送时间分组) - $pushHistoryQuery = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->join('workbench w', 'w.id = wgpi.workbenchId', 'left') - ->join('workbench_group_push wgp', 'wgp.workbenchId = wgpi.workbenchId', 'left') - ->join('content_item ci', 'ci.id = wgpi.contentId', 'left') - ->join('content_library cl', 'cl.id = ci.libraryId', 'left') - ->where($workbenchWhere) - ->field([ - 'wgpi.workbenchId', - 'w.name as workbenchName', - 'wgpi.contentId', - 'FROM_UNIXTIME(wgpi.createTime, "%Y-%m-%d %H:00:00") as pushTime', - 'wgpi.targetType', - 'wgp.groupPushSubType', - 'MIN(wgpi.createTime) as createTime', - 'COUNT(DISTINCT wgpi.id) as totalCount', - 'cl.name as contentLibraryName' - ]) - ->group('wgpi.workbenchId, wgpi.contentId, pushTime, wgpi.targetType, wgp.groupPushSubType'); - - if (!empty($keyword)) { - $pushHistoryQuery->where('w.name|cl.name|ci.content', 'like', '%' . $keyword . '%'); - } - - $pushHistoryList = $pushHistoryQuery->order('createTime', 'desc')->select(); - - // 2. 查询所有任务(包括未执行的) - $allTasksQuery = Db::name('workbench') - ->alias('w') - ->join('workbench_group_push wgp', 'wgp.workbenchId = w.id', 'left') - ->where($workbenchWhere) - ->field([ - 'w.id as workbenchId', - 'w.name as workbenchName', - 'w.createTime', - 'wgp.targetType', - 'wgp.groupPushSubType', - 'wgp.groups', - 'wgp.friends', - 'wgp.trafficPools' - ]); - - if (!empty($keyword)) { - $allTasksQuery->where('w.name', 'like', '%' . $keyword . '%'); - } - - $allTasks = $allTasksQuery->select(); - - // 3. 合并数据:已执行的推送记录 + 未执行的任务 - $resultList = []; - $executedWorkbenchIds = []; - - // 处理已执行的推送记录 - foreach ($pushHistoryList as $item) { - $itemWorkbenchId = $item['workbenchId']; - $contentId = $item['contentId']; - $pushTime = $item['pushTime']; - $targetType = intval($item['targetType']); - $groupPushSubType = isset($item['groupPushSubType']) ? intval($item['groupPushSubType']) : 1; - - // 标记该工作台已有执行记录 - if (!in_array($itemWorkbenchId, $executedWorkbenchIds)) { - $executedWorkbenchIds[] = $itemWorkbenchId; - } - - // 将时间字符串转换为时间戳范围(小时级别) - $pushTimeStart = strtotime($pushTime); - $pushTimeEnd = $pushTimeStart + 3600; // 一小时后 - - // 获取该次推送的详细统计 - $pushWhere = [ - ['wgpi.workbenchId', '=', $itemWorkbenchId], - ['wgpi.contentId', '=', $contentId], - ['wgpi.createTime', '>=', $pushTimeStart], - ['wgpi.createTime', '<', $pushTimeEnd], - ['wgpi.targetType', '=', $targetType] - ]; - - // 目标数量 - if ($targetType == 1) { - // 群推送:统计群数量 - $targetCount = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($pushWhere) - ->where('wgpi.groupId', '<>', null) - ->distinct(true) - ->count('wgpi.groupId'); - } else { - // 好友推送:统计好友数量 - $targetCount = Db::name('workbench_group_push_item') - ->alias('wgpi') - ->where($pushWhere) - ->where('wgpi.friendId', '<>', null) - ->distinct(true) - ->count('wgpi.friendId'); - } - - // 成功数和失败数(简化处理,实际需要根据发送状态判断) - $successCount = intval($item['totalCount']); // 简化处理 - $failCount = 0; // 简化处理,实际需要从发送状态获取 - - // 状态判断 - $itemStatus = $successCount > 0 ? 'success' : 'failed'; - if ($failCount > 0 && $successCount > 0) { - $itemStatus = 'partial'; - } - - // 推送类型判断 - $pushTypeText = ''; - $pushTypeCode = ''; - if ($targetType == 1) { - // 群推送 - if ($groupPushSubType == 2) { - $pushTypeText = '群公告'; - $pushTypeCode = 'announcement'; - } else { - $pushTypeText = '群消息'; - $pushTypeCode = 'group'; - } - } else { - // 好友推送 - $pushTypeText = '好友消息'; - $pushTypeCode = 'friend'; - } - - $resultList[] = [ - 'workbenchId' => $itemWorkbenchId, - 'taskName' => $item['workbenchName'] ?? '', - 'pushType' => $pushTypeText, - 'pushTypeCode' => $pushTypeCode, - 'targetCount' => $targetCount, - 'successCount' => $successCount, - 'failCount' => $failCount, - 'status' => $itemStatus, - 'statusText' => $this->getStatusText($itemStatus), - 'createTime' => date('Y-m-d H:i:s', $item['createTime']), - 'contentLibraryName' => $item['contentLibraryName'] ?? '' - ]; - } - - // 处理未执行的任务 - foreach ($allTasks as $task) { - $taskWorkbenchId = $task['workbenchId']; - - // 如果该任务已有执行记录,跳过(避免重复) - if (in_array($taskWorkbenchId, $executedWorkbenchIds)) { - continue; - } - - $targetType = isset($task['targetType']) ? intval($task['targetType']) : 1; - $groupPushSubType = isset($task['groupPushSubType']) ? intval($task['groupPushSubType']) : 1; - - // 计算目标数量(从配置中获取) - $targetCount = 0; - if ($targetType == 1) { - // 群推送:统计配置的群数量 - $groups = json_decode($task['groups'] ?? '[]', true); - $targetCount = is_array($groups) ? count($groups) : 0; - } else { - // 好友推送:统计配置的好友数量或流量池数量 - $friends = json_decode($task['friends'] ?? '[]', true); - $trafficPools = json_decode($task['trafficPools'] ?? '[]', true); - $friendCount = is_array($friends) ? count($friends) : 0; - $poolCount = is_array($trafficPools) ? count($trafficPools) : 0; - // 如果配置了流量池,目标数量暂时显示为流量池数量(实际数量需要从流量池中统计) - $targetCount = $friendCount > 0 ? $friendCount : $poolCount; - } - - // 推送类型判断 - $pushTypeText = ''; - $pushTypeCode = ''; - if ($targetType == 1) { - // 群推送 - if ($groupPushSubType == 2) { - $pushTypeText = '群公告'; - $pushTypeCode = 'announcement'; - } else { - $pushTypeText = '群消息'; - $pushTypeCode = 'group'; - } - } else { - // 好友推送 - $pushTypeText = '好友消息'; - $pushTypeCode = 'friend'; - } - - $resultList[] = [ - 'workbenchId' => $taskWorkbenchId, - 'taskName' => $task['workbenchName'] ?? '', - 'pushType' => $pushTypeText, - 'pushTypeCode' => $pushTypeCode, - 'targetCount' => $targetCount, - 'successCount' => 0, - 'failCount' => 0, - 'status' => 'pending', - 'statusText' => '进行中', - 'createTime' => date('Y-m-d H:i:s', $task['createTime']), - 'contentLibraryName' => '' - ]; - } - - // 应用筛选条件 - $filteredList = []; - foreach ($resultList as $item) { - // 推送类型筛选 - if (!empty($pushType)) { - if ($pushType === 'friend' && $item['pushTypeCode'] !== 'friend') { - continue; - } - if ($pushType === 'group' && $item['pushTypeCode'] !== 'group') { - continue; - } - if ($pushType === 'announcement' && $item['pushTypeCode'] !== 'announcement') { - continue; - } - } - - // 状态筛选 - if (!empty($status)) { - if ($status === 'success' && $item['status'] !== 'success') { - continue; - } - if ($status === 'progress') { - // 进行中:包括 partial 和 pending - if ($item['status'] !== 'partial' && $item['status'] !== 'pending') { - continue; - } - } - if ($status === 'failed' && $item['status'] !== 'failed') { - continue; - } - } - - $filteredList[] = $item; - } - - // 按创建时间倒序排序 - usort($filteredList, function($a, $b) { - return strtotime($b['createTime']) - strtotime($a['createTime']); - }); - - // 分页处理 - $total = count($filteredList); - $offset = ($page - 1) * $limit; - $list = array_slice($filteredList, $offset, $limit); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - /** - * 获取状态文本 - * @param string $status 状态码 - * @return string 状态文本 - */ - private function getStatusText($status) - { - $statusMap = [ - 'success' => '已完成', - 'partial' => '进行中', - 'pending' => '进行中', - 'failed' => '失败' - ]; - return $statusMap[$status] ?? '未知'; - } - - /** - * 规范化客服微信ID列表 - * @param mixed $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @param array $deviceGroups 设备ID数组 - * @return array - * @throws \Exception - */ - private function normalizeOwnerWechatIds($ownerWechatIds, WorkbenchGroupPush $originalConfig = null, array $deviceGroups = []): array - { - // 处理设备ID数组 - $deviceGroupsList = $this->extractIdList($deviceGroups, '设备参数格式错误'); - - if ($ownerWechatIds === null) { - $existing = $originalConfig ? $this->decodeJsonArray($originalConfig->ownerWechatIds ?? []) : []; - // 如果原有配置为空,且没有传设备ID,则报错 - if (empty($existing) && empty($deviceGroupsList)) { - throw new \Exception('请至少选择一个客服微信或设备'); - } - // 如果原有配置为空但有设备ID,返回空数组(允许使用设备ID) - if (empty($existing) && !empty($deviceGroupsList)) { - return []; - } - return $existing; - } - - if (!is_array($ownerWechatIds)) { - throw new \Exception('客服参数格式错误'); - } - - $normalized = $this->extractIdList($ownerWechatIds, '客服参数格式错误'); - // 如果 ownerWechatIds 为空,但传了设备ID,则允许(两个传一个即可) - if (empty($normalized) && empty($deviceGroupsList)) { - throw new \Exception('请至少选择一个客服微信或设备'); - } - return $normalized; - } - - /** - * 构建群推送配置数据 - * @param array $param - * @param array $ownerWechatIds - * @param WorkbenchGroupPush|null $originalConfig - * @return array - * @throws \Exception - */ - private function prepareGroupPushData(array $param, array $ownerWechatIds, WorkbenchGroupPush $originalConfig = null): array - { - $targetTypeDefault = $originalConfig ? intval($originalConfig->targetType) : 1; - $targetType = intval($this->getParamValue($param, 'targetType', $targetTypeDefault)) ?: 1; - - $groupPushSubTypeDefault = $originalConfig ? intval($originalConfig->groupPushSubType) : 1; - $groupPushSubType = intval($this->getParamValue($param, 'groupPushSubType', $groupPushSubTypeDefault)) ?: 1; - if (!in_array($groupPushSubType, [1, 2], true)) { - $groupPushSubType = 1; - } - - $data = [ - 'pushType' => $this->toBoolInt($this->getParamValue($param, 'pushType', $originalConfig->pushType ?? 0)), - 'targetType' => $targetType, - 'startTime' => $this->getParamValue($param, 'startTime', $originalConfig->startTime ?? ''), - 'endTime' => $this->getParamValue($param, 'endTime', $originalConfig->endTime ?? ''), - 'maxPerDay' => intval($this->getParamValue($param, 'maxPerDay', $originalConfig->maxPerDay ?? 0)), - 'pushOrder' => $this->getParamValue($param, 'pushOrder', $originalConfig->pushOrder ?? 1), - 'groupPushSubType' => $groupPushSubType, - 'status' => $this->toBoolInt($this->getParamValue($param, 'status', $originalConfig->status ?? 0)), - 'socialMediaId' => $this->getParamValue($param, 'socialMediaId', $originalConfig->socialMediaId ?? ''), - 'promotionSiteId' => $this->getParamValue($param, 'promotionSiteId', $originalConfig->promotionSiteId ?? ''), - 'friendIntervalMin' => intval($this->getParamValue($param, 'friendIntervalMin', $originalConfig->friendIntervalMin ?? 10)), - 'friendIntervalMax' => intval($this->getParamValue($param, 'friendIntervalMax', $originalConfig->friendIntervalMax ?? 20)), - 'messageIntervalMin' => intval($this->getParamValue($param, 'messageIntervalMin', $originalConfig->messageIntervalMin ?? 1)), - 'messageIntervalMax' => intval($this->getParamValue($param, 'messageIntervalMax', $originalConfig->messageIntervalMax ?? 12)), - 'isRandomTemplate' => $this->toBoolInt($this->getParamValue($param, 'isRandomTemplate', $originalConfig->isRandomTemplate ?? 0)), - 'ownerWechatIds' => json_encode($ownerWechatIds, JSON_UNESCAPED_UNICODE), - ]; - - // 处理设备ID数组(deviceGroups),保存到 devices 字段 - $deviceGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->devices ?? []) : []; - $deviceGroupsParam = $this->getParamValue($param, 'deviceGroups', null); - $deviceGroups = $deviceGroupsParam !== null - ? $this->extractIdList($deviceGroupsParam, '设备参数格式错误') - : $deviceGroupsExisting; - $data['devices'] = json_encode($deviceGroups, JSON_UNESCAPED_UNICODE); - - if ($data['friendIntervalMin'] > $data['friendIntervalMax']) { - throw new \Exception('目标间最小间隔不能大于最大间隔'); - } - if ($data['messageIntervalMin'] > $data['messageIntervalMax']) { - throw new \Exception('消息间最小间隔不能大于最大间隔'); - } - - // 群公告(groupPushSubType = 2)时,contentGroups 可以为空,不需要验证 - if ($targetType === 1 && $groupPushSubType === 2) { - // 群公告可以不传内容库,允许为空,不进行任何验证 - $contentGroupsParam = $this->getParamValue($param, 'contentGroups', null); - if ($contentGroupsParam !== null) { - // 如果传入了参数,尝试解析,但不验证格式和是否为空 - try { - $contentGroups = $this->extractIdList($contentGroupsParam, '内容库参数格式错误'); - } catch (\Exception $e) { - // 群公告时忽略格式错误,使用空数组 - $contentGroups = []; - } - } else { - // 如果没有传入参数,使用现有配置或空数组 - $contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : []; - $contentGroups = $contentGroupsExisting; - } - } else { - // 其他情况,正常处理并验证 - $contentGroupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->contentLibraries ?? []) : []; - $contentGroupsParam = $this->getParamValue($param, 'contentGroups', null); - $contentGroups = $contentGroupsParam !== null - ? $this->extractIdList($contentGroupsParam, '内容库参数格式错误') - : $contentGroupsExisting; - // 其他情况,内容库为必填 - if (empty($contentGroups)) { - throw new \Exception('请至少选择一个内容库'); - } - } - $data['contentLibraries'] = json_encode($contentGroups, JSON_UNESCAPED_UNICODE); - - $postPushTagsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->postPushTags ?? []) : []; - $postPushTagsParam = $this->getParamValue($param, 'postPushTags', null); - $postPushTags = $postPushTagsParam !== null - ? $this->extractIdList($postPushTagsParam, '推送标签参数格式错误') - : $postPushTagsExisting; - $data['postPushTags'] = json_encode($postPushTags, JSON_UNESCAPED_UNICODE); - - if ($targetType === 1) { - $data['isLoop'] = $this->toBoolInt($this->getParamValue($param, 'isLoop', $originalConfig->isLoop ?? 0)); - - $groupsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->groups ?? []) : []; - $wechatGroups = array_key_exists('wechatGroups', $param) - ? $this->extractIdList($param['wechatGroups'], '群参数格式错误') - : $groupsExisting; - if (empty($wechatGroups)) { - throw new \Exception('群推送必须选择微信群'); - } - $data['groups'] = json_encode($wechatGroups, JSON_UNESCAPED_UNICODE); - $data['friends'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode([], JSON_UNESCAPED_UNICODE); - - if ($groupPushSubType === 2) { - $announcementContent = $this->getParamValue($param, 'announcementContent', $originalConfig->announcementContent ?? ''); - if (empty($announcementContent)) { - throw new \Exception('群公告必须输入公告内容'); - } - $enableAiRewrite = $this->toBoolInt($this->getParamValue($param, 'enableAiRewrite', $originalConfig->enableAiRewrite ?? 0)); - $aiRewritePrompt = trim((string)$this->getParamValue($param, 'aiRewritePrompt', $originalConfig->aiRewritePrompt ?? '')); - if ($enableAiRewrite === 1 && $aiRewritePrompt === '') { - throw new \Exception('启用AI智能话术改写时,必须输入改写提示词'); - } - $data['announcementContent'] = $announcementContent; - $data['enableAiRewrite'] = $enableAiRewrite; - $data['aiRewritePrompt'] = $aiRewritePrompt; - } else { - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - } else { - $data['isLoop'] = 0; - $friendsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->friends ?? []) : []; - $trafficPoolsExisting = $originalConfig ? $this->decodeJsonArray($originalConfig->trafficPools ?? []) : []; - - $friendTargets = array_key_exists('wechatFriends', $param) - ? $this->extractIdList($param['wechatFriends'], '好友参数格式错误') - : $friendsExisting; - $trafficPools = array_key_exists('trafficPools', $param) - ? $this->extractIdList($param['trafficPools'], '流量池参数格式错误') - : $trafficPoolsExisting; - - if (empty($friendTargets) && empty($trafficPools)) { - throw new \Exception('好友推送需至少选择好友或流量池'); - } - - $data['friends'] = json_encode($friendTargets, JSON_UNESCAPED_UNICODE); - $data['trafficPools'] = json_encode($trafficPools, JSON_UNESCAPED_UNICODE); - $data['groups'] = json_encode([], JSON_UNESCAPED_UNICODE); - $data['groupPushSubType'] = 1; - $data['announcementContent'] = ''; - $data['enableAiRewrite'] = 0; - $data['aiRewritePrompt'] = ''; - } - - return $data; - } - - /** - * 获取参数值,若不存在则返回默认值 - * @param array $param - * @param string $key - * @param mixed $default - * @return mixed - */ - private function getParamValue(array $param, string $key, $default) - { - return array_key_exists($key, $param) ? $param[$key] : $default; - } - - /** - * 将值转换为整型布尔 - * @param mixed $value - * @return int - */ - private function toBoolInt($value): int - { - return empty($value) ? 0 : 1; - } - - /** - * 从参数中提取ID列表 - * @param mixed $items - * @param string $errorMessage - * @return array - * @throws \Exception - */ - private function extractIdList($items, string $errorMessage = '参数格式错误'): array - { - if (!is_array($items)) { - throw new \Exception($errorMessage); - } - - $ids = []; - foreach ($items as $item) { - if (is_array($item) && isset($item['id'])) { - $item = $item['id']; - } - if ($item === '' || $item === null) { - continue; - } - $ids[] = $item; - } - - return array_values(array_unique($ids)); - } - - /** - * 解码JSON数组 - * @param mixed $value - * @return array - */ - private function decodeJsonArray($value): array - { - if (empty($value)) { - return []; - } - if (is_array($value)) { - return $value; - } - $decoded = json_decode($value, true); - return is_array($decoded) ? $decoded : []; - } - - /** - * 验证内容是否包含链接 - * @param string $content 要检测的内容 - * @return bool - */ - private function containsLink($content) - { - // 定义各种链接的正则表达式模式 - $patterns = [ - // HTTP/HTTPS链接 - '/https?:\/\/[^\s]+/i', - // 京东商品链接 - '/item\.jd\.com\/\d+/i', - // 京东短链接 - '/u\.jd\.com\/[a-zA-Z0-9]+/i', - // 淘宝商品链接 - '/item\.taobao\.com\/item\.htm\?id=\d+/i', - // 天猫商品链接 - '/detail\.tmall\.com\/item\.htm\?id=\d+/i', - // 淘宝短链接 - '/m\.tb\.cn\/[a-zA-Z0-9]+/i', - // 拼多多链接 - '/mobile\.yangkeduo\.com\/goods\.html\?goods_id=\d+/i', - // 苏宁易购链接 - '/product\.suning\.com\/\d+\/\d+\.html/i', - // 通用域名模式(包含常见电商域名) - '/(?:jd|taobao|tmall|yangkeduo|suning|amazon|dangdang)\.com[^\s]*/i', - // 通用短链接模式 - '/[a-zA-Z0-9-]+\.[a-zA-Z]{2,}\/[a-zA-Z0-9\-._~:\/?#\[\]@!$&\'()*+,;=]+/i' - ]; - - // 遍历所有模式进行匹配 - foreach ($patterns as $pattern) { - if (preg_match($pattern, $content)) { - return true; - } - } - - return false; - } } - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 获取已创建的群ID列表(状态为成功且groupId不为空) - $groupIds = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->where('groupId', '<>', null) - ->group('groupId') - ->column('groupId'); - - if (empty($groupIds)) { - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => [], - 'total' => 0, - 'page' => $page, - 'limit' => $limit - ] - ]); - } - - // 查询群组详细信息(从s2_wechat_chatroom表查询) - $query = Db::table('s2_wechat_chatroom')->alias('wc') - ->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left') - ->where('wc.id', 'in', $groupIds) - ->where('wc.isDeleted', 0); - - // 关键字搜索 - if (!empty($keyword)) { - $query->where(function ($q) use ($keyword) { - $q->where('wc.nickname', 'like', '%' . $keyword . '%') - ->whereOr('wc.chatroomId', 'like', '%' . $keyword . '%') - ->whereOr('wa.nickName', 'like', '%' . $keyword . '%'); - }); - } - - $total = $query->count(); - $list = $query->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias') - ->order('wc.createTime', 'desc') - ->page($page, $limit) - ->select(); - - // 统计每个群的成员数量和成员信息 - foreach ($list as &$item) { - // 统计该群的成员数量(从workbench_group_create_item表统计) - $memberCount = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $item['id']) - ->where('status', 'in', [2, 4]) // 创建成功和管理员好友已拉入 - ->count(); - - // 获取成员列表(用于显示成员头像) - $memberList = Db::name('workbench_group_create_item')->alias('wgci') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left') - ->where('wgci.workbenchId', $workbenchId) - ->where('wgci.groupId', $item['id']) - ->where('wgci.status', 'in', [2, 4]) - ->field('wf.avatar, wf.wechatId, wf.nickname') - ->order('wgci.createTime', 'asc') - ->limit(10) // 最多显示10个成员头像 - ->select(); - - // 格式化成员头像列表 - $memberAvatars = []; - foreach ($memberList as $member) { - if (!empty($member['avatar'])) { - $memberAvatars[] = [ - 'avatar' => $member['avatar'], - 'wechatId' => $member['wechatId'] ?? '', - 'nickname' => $member['nickname'] ?? '' - ]; - } - } - - // 计算剩余成员数(用于显示"+XX") - $remainingCount = $memberCount > count($memberAvatars) ? $memberCount - count($memberAvatars) : 0; - - // 格式化返回数据 - $item['memberCount'] = $memberCount; - $item['memberCountText'] = $memberCount . '人'; // 格式化为"XX人" - $item['createTime'] = !empty($item['createTime']) ? date('Y-m-d', $item['createTime']) : ''; // 格式化为"YYYY-MM-DD" - $item['memberAvatars'] = $memberAvatars; // 成员头像列表(最多10个) - $item['remainingCount'] = $remainingCount; // 剩余成员数(用于显示"+XX") - - // 保留原有字段,但调整格式 - $item['groupName'] = $item['groupName'] ?? ''; - $item['groupAvatar'] = $item['groupAvatar'] ?? ''; - } - unset($item); - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => [ - 'list' => $list, - 'total' => $total, - 'page' => $page, - 'limit' => $limit - ] - ]); - } -} - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 验证该群是否属于该工作台 - $groupItem = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $groupId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->find(); - - if (empty($groupItem)) { - return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']); - } - - // 查询群基本信息(从s2_wechat_chatroom表查询) - $group = Db::table('s2_wechat_chatroom')->alias('wc') - ->join('wechat_account wa', 'wa.wechatId = wc.wechatAccountWechatId', 'left') - ->where('wc.id', $groupId) - ->where('wc.isDeleted', 0) - ->field('wc.id,wc.nickname as groupName,wc.chatroomId,wc.chatroomAvatar as groupAvatar,wc.wechatAccountWechatId as ownerWechatId,wc.createTime,wc.chatroomOwnerNickname as ownerNickname,wc.chatroomOwnerAvatar as ownerAvatar,wa.alias as ownerAlias,wc.announce') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - // 获取chatroomId - $chatroomId = $group['chatroomId'] ?? ''; - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - // 从s2_wechat_chatroom_member表查询所有成员列表(不限制数量) - $memberList = Db::table('s2_wechat_chatroom_member')->alias('wcm') - ->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = wcm.wechatId', 'left') - ->where('wcm.chatroomId', $chatroomId) - ->field('wcm.wechatId,wcm.nickname as memberNickname,wcm.avatar as memberAvatar,wcm.conRemark as memberRemark,wcm.alias as memberAlias,wcm.createTime as joinTime,wcm.updateTime,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar') - ->order('wcm.createTime', 'asc') - ->select(); - - // 去重:按wechatId去重,保留第一条记录 - $memberMap = []; - foreach ($memberList as $member) { - $wechatId = $member['wechatId'] ?? ''; - if (!empty($wechatId) && !isset($memberMap[$wechatId])) { - $memberMap[$wechatId] = $member; - } - } - $memberList = array_values($memberMap); // 重新索引数组 - - // 获取在群中的成员wechatId列表(用于判断是否已退群) - $inGroupWechatIds = array_column($memberList, 'wechatId'); - $inGroupWechatIds = array_filter($inGroupWechatIds); // 过滤空值 - - // 获取通过自动建群加入的成员信息(用于判断入群状态和已退群成员) - $autoJoinMemberList = Db::name('workbench_group_create_item') - ->alias('wgci') - ->join(['s2_wechat_friend' => 'wf'], 'wf.id = wgci.friendId', 'left') - ->where('wgci.workbenchId', $workbenchId) - ->where('wgci.groupId', $groupId) - ->where('wgci.status', 'in', [2, 4]) // 创建成功和管理员好友已拉入 - ->field('wf.wechatId,wf.id as friendId,wf.nickname as friendNickname,wf.avatar as friendAvatar,wgci.createTime as autoJoinTime') - ->select(); - - // 去重:按wechatId去重,保留第一条记录 - $autoJoinMemberMap = []; - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - if (!empty($wechatId) && !isset($autoJoinMemberMap[$wechatId])) { - $autoJoinMemberMap[$wechatId] = $autoMember; - } - } - $autoJoinMemberList = array_values($autoJoinMemberMap); // 重新索引数组 - - // 统计成员总数(包括在群中的成员和已退群的成员) - // 先统计在群中的成员数 - $inGroupCount = count($memberList); - - // 统计已退群的成员数(在自动建群记录中但不在群中的成员) - $quitCount = 0; - if (!empty($autoJoinMemberList)) { - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - if (!empty($wechatId) && !in_array($wechatId, $inGroupWechatIds)) { - $quitCount++; - } - } - } - - // 总成员数 = 在群中的成员数 + 已退群的成员数 - $memberCount = $inGroupCount + $quitCount; - - // 获取自动建群加入的成员wechatId列表(用于判断入群状态) - $autoJoinWechatIds = array_column($autoJoinMemberList, 'wechatId'); - $autoJoinWechatIds = array_filter($autoJoinWechatIds); // 过滤空值 - - // 格式化在群中的成员列表 - $members = []; - $addedWechatIds = []; // 用于记录已添加的成员wechatId,避免重复 - $ownerWechatId = $group['ownerWechatId'] ?? ''; - foreach ($memberList as $member) { - $wechatId = $member['wechatId'] ?? ''; - - // 跳过空wechatId和已添加的成员 - if (empty($wechatId) || in_array($wechatId, $addedWechatIds)) { - continue; - } - - // 标记为已添加 - $addedWechatIds[] = $wechatId; - - // 根据wechatId判断是否为群主 - $isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0; - - // 判断入群状态:如果在自动建群记录中,说明是通过自动建群加入的;否则是其他方式加入的 - $joinStatus = in_array($wechatId, $autoJoinWechatIds) ? 'auto' : 'manual'; - - // 判断是否已退群:如果成员在s2_wechat_chatroom_member表中存在,说明在群中;否则已退群 - // 由于我们已经从s2_wechat_chatroom_member表查询,所以这里都是"在群中"状态 - $isQuit = 0; // 0=在群中,1=已退群 - - // 优先使用friend表的昵称和头像,如果没有则使用member表的 - $nickname = !empty($member['friendNickname']) ? $member['friendNickname'] : ($member['memberNickname'] ?? ''); - $avatar = !empty($member['friendAvatar']) ? $member['friendAvatar'] : ($member['memberAvatar'] ?? ''); - - $members[] = [ - 'friendId' => $member['friendId'] ?? 0, - 'wechatId' => $wechatId, - 'nickname' => $nickname, - 'avatar' => $avatar, - 'alias' => $member['memberAlias'] ?? '', - 'remark' => $member['memberRemark'] ?? '', - 'isOwner' => $isOwner, // 标记群主 - 'joinStatus' => $joinStatus, // 入群状态:auto=自动建群加入,manual=其他方式加入 - 'isQuit' => $isQuit, // 是否已退群:0=在群中,1=已退群 - 'joinTime' => !empty($member['joinTime']) ? date('Y-m-d H:i:s', $member['joinTime']) : '', // 入群时间 - ]; - } - - // 添加已退群的成员(在自动建群记录中但不在群中的成员) - foreach ($autoJoinMemberList as $autoMember) { - $wechatId = $autoMember['wechatId'] ?? ''; - - // 跳过空wechatId、已在群中的成员和已添加的成员 - if (empty($wechatId) || in_array($wechatId, $inGroupWechatIds) || in_array($wechatId, $addedWechatIds)) { - continue; - } - - // 标记为已添加 - $addedWechatIds[] = $wechatId; - - // 根据wechatId判断是否为群主 - $isOwner = (!empty($ownerWechatId) && $wechatId == $ownerWechatId) ? 1 : 0; - - $members[] = [ - 'friendId' => $autoMember['friendId'] ?? 0, - 'wechatId' => $wechatId, - 'nickname' => $autoMember['friendNickname'] ?? '', - 'avatar' => $autoMember['friendAvatar'] ?? '', - 'alias' => '', - 'remark' => '', - 'isOwner' => $isOwner, // 标记群主 - 'joinStatus' => 'auto', // 入群状态:auto=自动建群加入 - 'isQuit' => 1, // 是否已退群:1=已退群 - 'joinTime' => !empty($autoMember['autoJoinTime']) ? date('Y-m-d H:i:s', $autoMember['autoJoinTime']) : '', // 入群时间 - ]; - } - - // 将群主排在第一位 - usort($members, function($a, $b) { - if ($a['isOwner'] == $b['isOwner']) { - return 0; - } - return $a['isOwner'] > $b['isOwner'] ? -1 : 1; - }); - - // 格式化返回数据 - $result = [ - 'id' => $group['id'], - 'groupName' => $group['groupName'] ?? '', - 'chatroomId' => $group['chatroomId'] ?? '', - 'groupAvatar' => $group['groupAvatar'] ?? '', - 'ownerWechatId' => $group['ownerWechatId'] ?? '', - 'ownerNickname' => $group['ownerNickname'] ?? '', - 'ownerAvatar' => $group['ownerAvatar'] ?? '', - 'ownerAlias' => $group['ownerAlias'] ?? '', - 'announce' => $group['announce'] ?? '', - 'createTime' => !empty($group['createTime']) ? date('Y-m-d H:i', $group['createTime']) : '', // 格式化为"YYYY-MM-DD HH:MM" - 'memberCount' => $memberCount, - 'memberCountText' => $memberCount . '人', // 格式化为"XX人" - 'workbenchName' => $workbench->name ?? '', // 任务名称(工作台名称) - 'members' => $members // 所有成员列表 - ]; - - return json([ - 'code' => 200, - 'msg' => '获取成功', - 'data' => $result - ]); - } - - /** - * 同步群最新信息(包括群成员) - * @return \think\response\Json - */ - public function syncGroupInfo() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 验证工作台权限 - $where = [ - ['id', '=', $workbenchId], - ['companyId', '=', $this->request->userInfo['companyId']], - ['type', '=', self::TYPE_GROUP_CREATE], - ['isDel', '=', 0] - ]; - if (empty($this->request->userInfo['isAdmin'])) { - $where[] = ['userId', '=', $this->request->userInfo['id']]; - } - - $workbench = Workbench::where($where)->find(); - if (empty($workbench)) { - return json(['code' => 404, 'msg' => '工作台不存在或无权限']); - } - - // 验证该群是否属于该工作台 - $groupItem = Db::name('workbench_group_create_item') - ->where('workbenchId', $workbenchId) - ->where('groupId', $groupId) - ->where('status', 2) // STATUS_SUCCESS = 2 - ->find(); - - if (empty($groupItem)) { - return json(['code' => 404, 'msg' => '群不存在或不属于该工作台']); - } - - // 查询群基本信息,获取chatroomId和wechatAccountWechatId - $group = Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->where('isDeleted', 0) - ->field('id,chatroomId,wechatAccountWechatId') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - $chatroomId = $group['chatroomId'] ?? ''; - $wechatAccountWechatId = $group['wechatAccountWechatId'] ?? ''; - - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - try { - // 实例化WechatChatroomController - $chatroomController = new \app\api\controller\WechatChatroomController(); - - // 1. 同步群信息(调用getlist方法) - $syncData = [ - 'wechatChatroomId' => $chatroomId, // 使用chatroomId作为wechatChatroomId来指定要同步的群 - 'wechatAccountKeyword' => $wechatAccountWechatId, // 通过群主微信ID筛选 - 'isDeleted' => false, - 'pageIndex' => 1, - 'pageSize' => 100 // 获取足够多的数据 - ]; - $syncResult = $chatroomController->getlist($syncData, true, 0); // isInner = true - $syncResponse = json_decode($syncResult, true); - - if (empty($syncResponse['code']) || $syncResponse['code'] != 200) { - return json(['code' => 500, 'msg' => '同步群信息失败:' . ($syncResponse['msg'] ?? '未知错误')]); - } - - // 2. 同步群成员信息(调用listChatroomMember方法) - // wechatChatroomId 使用 s2_wechat_chatroom 表的 id(即groupId) - // chatroomId 使用群聊ID(chatroomId) - $wechatChatroomId = $groupId; // s2_wechat_chatroom表的id - $memberSyncResult = $chatroomController->listChatroomMember($wechatChatroomId, $chatroomId, true); // isInner = true - $memberSyncResponse = json_decode($memberSyncResult, true); - - if (empty($memberSyncResponse['code']) || $memberSyncResponse['code'] != 200) { - // 成员同步失败不影响整体结果,记录警告即可 - \think\facade\Log::warning("同步群成员失败。群ID: {$groupId}, 群聊ID: {$chatroomId}, 错误: " . ($memberSyncResponse['msg'] ?? '未知错误')); - } - - return json([ - 'code' => 200, - 'msg' => '同步成功', - 'data' => [ - 'groupId' => $groupId, - 'chatroomId' => $chatroomId, - 'groupInfoSynced' => true, - 'memberInfoSynced' => !empty($memberSyncResponse['code']) && $memberSyncResponse['code'] == 200 - ] - ]); - } catch (\Exception $e) { - \think\facade\Log::error("同步群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage()); - return json(['code' => 500, 'msg' => '同步失败:' . $e->getMessage()]); - } - } - - /** - * 修改群名称、群公告 - * @return \think\response\Json - */ - public function modifyGroupInfo() - { - $workbenchId = $this->request->param('workbenchId', 0); - $groupId = $this->request->param('groupId', 0); - $chatroomName = $this->request->param('chatroomName', ''); - $announce = $this->request->param('announce', ''); - - if (empty($workbenchId)) { - return json(['code' => 400, 'msg' => '工作台ID不能为空']); - } - - if (empty($groupId)) { - return json(['code' => 400, 'msg' => '群ID不能为空']); - } - - // 至少需要提供一个修改项 - if (empty($chatroomName) && empty($announce)) { - return json(['code' => 400, 'msg' => '请至少提供群名称或群公告中的一个参数']); - } - - // 查询群基本信息 - $group = Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->where('isDeleted', 0) - ->field('id,chatroomId,wechatAccountWechatId,accountId,wechatAccountId') - ->find(); - - if (empty($group)) { - return json(['code' => 404, 'msg' => '群不存在']); - } - - $chatroomId = $group['id'] ?? ''; - - if (empty($chatroomId)) { - return json(['code' => 400, 'msg' => '群聊ID不存在']); - } - - try { - // 直接使用群表中的账号信息 - $executeAccountId = $group['accountId'] ?? 0; - $executeWechatAccountId = $group['wechatAccountId'] ?? 0; - $executeWechatId = $group['wechatAccountWechatId'] ?? ''; - - // 确保 wechatId 不为空 - if (empty($executeWechatId)) { - return json(['code' => 400, 'msg' => '无法获取微信账号ID']); - } - - // 调用 WebSocketController 修改群信息 - // 获取系统API账号信息(用于WebSocket连接) - $username = Env::get('api.username2', ''); - $password = Env::get('api.password2', ''); - - if (empty($username) || empty($password)) { - return json(['code' => 500, 'msg' => '系统API账号配置缺失']); - } - - // 获取系统账号ID - $systemAccountId = Db::name('users')->where('account', $username)->value('s2_accountId'); - if (empty($systemAccountId)) { - return json(['code' => 500, 'msg' => '未找到系统账号ID']); - } - - $webSocketController = new WebSocketController([ - 'userName' => $username, - 'password' => $password, - 'accountId' => $systemAccountId - ]); - // 构建修改参数 - $modifyData = [ - 'wechatChatroomId' => $chatroomId, - 'wechatAccountId' => $executeWechatAccountId, - ]; - if (!empty($chatroomName)) { - $modifyData['chatroomName'] = $chatroomName; - } - if (!empty($announce)) { - $modifyData['announce'] = $announce; - } - - - $modifyResult = $webSocketController->CmdChatroomModifyInfo($modifyData); - $modifyResponse = json_decode($modifyResult, true); - if (empty($modifyResponse['code']) || $modifyResponse['code'] != 200) { - return json(['code' => 500, 'msg' => '修改群信息失败:' . ($modifyResponse['msg'] ?? '未知错误')]); - } - - // 修改成功后更新数据库 - $updateData = [ - 'updateTime' => time() - ]; - - // 如果修改了群名称,更新数据库 - if (!empty($chatroomName)) { - $updateData['nickname'] = $chatroomName; - } - - // 如果修改了群公告,更新数据库 - if (!empty($announce)) { - $updateData['announce'] = $announce; - } - - // 更新数据库 - Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->update($updateData); - - return json([ - 'code' => 200, - 'msg' => '修改成功', - 'data' => [ - 'groupId' => $groupId, - 'chatroomId' => $chatroomId, - 'chatroomName' => $chatroomName, - 'announce' => $announce - ] - ]); - } catch (\Exception $e) { - \think\facade\Log::error("修改群信息异常。群ID: {$groupId}, 错误: " . $e->getMessage()); - return json(['code' => 500, 'msg' => '修改失败:' . $e->getMessage()]); - } - } - - /** - * 转移群聊到指定账号 - * @param int $groupId 群ID(s2_wechat_chatroom表的id) - * @param string $chatroomId 群聊ID - * @param int $toAccountId 目标账号ID(s2_company_account表的id) - * @param int $toWechatAccountId 目标微信账号ID(s2_wechat_account表的id) - * @return array ['success' => bool, 'msg' => string] - */ - protected function transferChatroomToAccount($groupId, $chatroomId, $toAccountId, $toWechatAccountId) - { - try { - // 查询目标账号信息 - $targetAccount = Db::table('s2_company_account') - ->where('id', $toAccountId) - ->field('id,userName,realName,nickname') - ->find(); - - if (empty($targetAccount)) { - return ['success' => false, 'msg' => '目标账号不存在']; - } - - // 查询目标微信账号信息 - $targetWechatAccount = Db::table('s2_wechat_account') - ->where('id', $toWechatAccountId) - ->field('id,wechatId,deviceAccountId') - ->find(); - - if (empty($targetWechatAccount)) { - return ['success' => false, 'msg' => '目标微信账号不存在']; - } - - // 调用 AutomaticAssign 进行群聊转移 - $automaticAssign = new \app\api\controller\AutomaticAssign(); - - // 构建转移参数(通过 API 调用) - $transferData = [ - 'wechatChatroomId' => $chatroomId, // 使用群聊ID - 'toAccountId' => $toAccountId, - 'wechatAccountKeyword' => $targetWechatAccount['wechatId'] ?? '', - 'isDeleted' => false - ]; - - // 直接更新数据库(因为 API 可能不支持指定单个群聊ID转移) - // 更新 s2_wechat_chatroom 表的 accountId 和 wechatAccountId - Db::table('s2_wechat_chatroom') - ->where('id', $groupId) - ->update([ - 'accountId' => $toAccountId, - 'accountUserName' => $targetAccount['userName'] ?? '', - 'accountRealName' => $targetAccount['realName'] ?? '', - 'accountNickname' => $targetAccount['nickname'] ?? '', - 'wechatAccountId' => $toWechatAccountId, - 'wechatAccountWechatId' => $targetWechatAccount['wechatId'] ?? '', - 'updateTime' => time() - ]); - - return ['success' => true, 'msg' => '转移成功']; - } catch (\Exception $e) { - \think\facade\Log::error("转移群聊异常。群ID: {$groupId}, 目标账号ID: {$toAccountId}, 错误: " . $e->getMessage()); - return ['success' => false, 'msg' => $e->getMessage()]; - } - } - - -} \ No newline at end of file diff --git a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php index 1e727003..cef710d2 100644 --- a/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php +++ b/Server/extend/WeChatDeviceApi/Adapters/ChuKeBao/Adapter.php @@ -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 通过的好友ID(s2_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 = '')