This commit is contained in:
Your Name
2026-04-21 09:49:44 +08:00
parent c9e0419895
commit c15c348b7a
454 changed files with 5391 additions and 25651 deletions
@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\qywx;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\lists\qywx\MsgArchiveLists;
use app\adminapi\lists\qywx\MsgSendTaskLists;
use app\adminapi\lists\qywx\MsgSessionLists;
use app\adminapi\logic\qywx\MessageLogic;
use app\adminapi\validate\qywx\MessageValidate;
use app\common\service\wechat\QywxMsgArchiveService;
use app\common\service\wechat\WechatWorkService;
use think\facade\Log;
/**
* 企业微信 员工↔客户 消息收发
*
* 接口一览:
* GET qywx.message/session_list 会话列表(左侧)
* GET qywx.message/archive_list 会话消息历史(中间)
* POST qywx.message/mark_read 清零会话未读
* POST qywx.message/send 创建企业群发(员工代发)任务
* GET qywx.message/send_task_list 群发任务列表
* GET qywx.message/send_task_detail 任务送达详情(会回调企微查询)
* GET qywx.message/staff_list 可代发员工(admin 表 work_wechat_userid 不空者)
* GET qywx.message/customer_of_staff 某员工已添加的客户
* POST qywx.message/upload_to_qywx 上传媒体文件到企微,返回 media_id(供附件使用)
* POST qywx.message/pull_archive 手动触发一次存档拉取(便于调试;cron 正常也会跑)
* GET qywx.message/archive_status 会话存档模块状态(SDK/私钥诊断)
*/
class MessageController extends BaseAdminController
{
public function session_list()
{
return $this->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 文件字段 filequery / 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());
}
}
@@ -27,6 +27,7 @@ class PrescriptionOrderController extends BaseAdminController
{
$params = (new PrescriptionOrderValidate())->get()->goCheck('paidPayOrders');
$exceptPo = (int) $this->request->get('prescription_order_id', 0);
$lists = OrderLogic::listPaidOrdersForDiagnosis(
(int) $params['diagnosis_id'],
$this->adminId,