dataLists(new MsgSessionLists()); } public function archive_list() { return $this->dataLists(new MsgArchiveLists()); } public function mark_read() { $sessionId = (int) $this->request->post('session_id', 0); if ($sessionId <= 0) { return $this->fail('session_id 无效'); } MessageLogic::markSessionRead($sessionId); return $this->success('ok'); } public function send() { $params = (new MessageValidate())->post()->goCheck('send'); $result = MessageLogic::createSendTask($params, $this->adminId); if ($result === false) { return $this->fail(MessageLogic::getError()); } return $this->success('已提交,员工手机端确认后将发出', $result); } public function send_task_list() { return $this->dataLists(new MsgSendTaskLists()); } public function send_task_detail() { $taskId = (int) $this->request->get('task_id', 0); $cursor = (string) $this->request->get('cursor', ''); if ($taskId <= 0) { return $this->fail('task_id 无效'); } return $this->data(MessageLogic::querySendTaskResult($taskId, $cursor)); } public function staff_list() { $keyword = trim((string) $this->request->get('keyword', '')); return $this->data(MessageLogic::staffList($keyword)); } public function customer_of_staff() { $staff = trim((string) $this->request->get('staff_userid', '')); $keyword = trim((string) $this->request->get('keyword', '')); $limit = (int) $this->request->get('limit', 200); return $this->data(MessageLogic::customerOfStaff($staff, $keyword, $limit)); } /** * 上传素材到企业微信,返回 media_id(3 天有效) * * 入参:multipart 文件字段 file,query / form 字段 type = image|voice|video|file * * 使用场景:前端先上传本地文件到自家服务器(/upload/image 等)得到本地 URL 用于预览; * 确认发送时再调本接口上传到企微拿 media_id,填入 add_msg_template 的 attachments。 */ public function upload_to_qywx() { $type = (string) $this->request->param('type', 'image'); if (!in_array($type, ['image', 'voice', 'video', 'file'], true)) { return $this->fail('不支持的媒体类型'); } $file = $this->request->file('file'); if (!$file) { return $this->fail('未上传文件'); } try { $tempDir = runtime_path() . 'qywx_upload_tmp' . DIRECTORY_SEPARATOR; if (!is_dir($tempDir)) { @mkdir($tempDir, 0755, true); } $savePath = $tempDir . uniqid('qywx_', true) . '_' . $file->getOriginalName(); // @phpstan-ignore-next-line move returns File move_uploaded_file($file->getPathname(), $savePath); $service = new WechatWorkService('customer_contact'); $resp = $service->uploadMedia($type, $savePath, $file->getOriginalName()); @unlink($savePath); $errcode = isset($resp['errcode']) ? (int) $resp['errcode'] : -1; if ($errcode !== 0 || empty($resp['media_id'])) { Log::warning('企微上传素材失败: ' . json_encode($resp, JSON_UNESCAPED_UNICODE)); return $this->fail('企微上传失败: ' . ($resp['errmsg'] ?? '未知错误')); } return $this->success('ok', [ 'media_id' => (string) $resp['media_id'], 'type' => $resp['type'] ?? $type, 'created_at' => $resp['created_at'] ?? time(), ]); } catch (\Throwable $e) { Log::error('upload_to_qywx 异常: ' . $e->getMessage()); return $this->fail($e->getMessage()); } } public function pull_archive() { $maxBatches = (int) $this->request->param('max_batches', 5); $download = (bool) $this->request->param('download', false); $pull = QywxMsgArchiveService::pullLoop(max(1, $maxBatches)); $media = null; if ($download && $pull['enabled']) { $media = QywxMsgArchiveService::downloadPendingMedia(200); } return $this->data([ 'pull' => $pull, 'media' => $media, ]); } public function archive_status() { return $this->data(MessageLogic::archiveStatus()); } }