This commit is contained in:
2026-08-05 17:38:29 +08:00
parent e831ce810b
commit 98b60c333a
5 changed files with 145 additions and 4 deletions

View File

@@ -5,6 +5,7 @@ namespace app\admin\controller;
use app\BaseController; use app\BaseController;
use app\common\model\Tickets; use app\common\model\Tickets;
use app\common\service\TicketNotifier;
use app\common\service\TicketService; use app\common\service\TicketService;
use think\facade\Session; use think\facade\Session;
@@ -109,8 +110,14 @@ class Ticket extends BaseController
$ticket = Tickets::find($id); $ticket = Tickets::find($id);
if (!$ticket) return $this->error('工单不存在'); if (!$ticket) return $this->error('工单不存在');
return $ticket->delete() $deleted = $ticket->delete();
? $this->success('删除成功', []) if (!$deleted) {
: $this->error('删除失败', []); return $this->error('删除失败', []);
}
// 通知前端实时刷新(静默,广播服务不可用不影响删除流程)
TicketNotifier::notify($id, 'delete');
return $this->success('删除成功', []);
} }
} }

View File

@@ -0,0 +1,50 @@
<?php
namespace app\common\service;
/**
* 工单变更通知(通过 Workerman 广播服务推送到前端)
*
* 重要约定:通知失败绝不能影响主业务流程,因此所有异常静默处理。
* 广播服务未启动时,新增/更新工单照常执行,只是前端不实时刷新。
*/
class TicketNotifier
{
/** 广播服务内部触发地址(仅监听本机) */
private const PUSH_URL = 'http://127.0.0.1:2347/push';
/** 请求超时(秒),不能太长以免拖慢主流程 */
private const TIMEOUT = 2;
/**
* 广播工单变更
*
* @param int|string $ticketId 工单ID
* @param string $action create|update|delete
* @return bool 通知是否成功发出false 不表示业务失败)
*/
public static function notify($ticketId, $action = 'create')
{
$payload = json_encode([
'action' => (string)$action,
'ticket_id' => (int)$ticketId,
], JSON_UNESCAPED_UNICODE);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $payload,
'timeout' => self::TIMEOUT,
'ignore_errors' => true,
],
]);
try {
$result = @file_get_contents(self::PUSH_URL, false, $context);
return $result !== false;
} catch (\Throwable $e) {
return false;
}
}
}

View File

@@ -89,6 +89,10 @@ class TicketService
// 当前无相同网站 // 当前无相同网站
$ticketResult = Tickets::create($data); $ticketResult = Tickets::create($data);
// 通知前端实时刷新(静默,广播服务不可用不影响主流程)
TicketNotifier::notify($ticketResult->id, 'create');
return $ticketResult; return $ticketResult;
} catch (\Exception $e) { } catch (\Exception $e) {
Cache::delete($lockKey); Cache::delete($lockKey);

View File

@@ -24,7 +24,8 @@
"topthink/framework": "^8.0", "topthink/framework": "^8.0",
"topthink/think-orm": "^3.0|^4.0", "topthink/think-orm": "^3.0|^4.0",
"topthink/think-filesystem": "^2.0|^3.0", "topthink/think-filesystem": "^2.0|^3.0",
"topthink/think-multi-app": "^1.1" "topthink/think-multi-app": "^1.1",
"workerman/workerman": "^4.2"
}, },
"require-dev": { "require-dev": {
"topthink/think-dumper": "^1.0", "topthink/think-dumper": "^1.0",

79
worker/websocket.php Normal file
View File

@@ -0,0 +1,79 @@
<?php
/**
* Workerman 广播服务
* ------------------------------------------------------------
* 启动: php worker/websocket.php start
* 停止: 前台运行按 Ctrl+C 即可Windows 不支持 stop 命令)
*
* 端口:
* 2346 websocket 端口,员工前端长连接(只推送,不做业务)
* 2347 http 触发端口,仅监听 127.0.0.1,供 ThinkPHP 内部调用
*
* 触发方式POST http://127.0.0.1:2347/push
* body: {"action":"create","ticket_id":123}
*
* 注意:
* - Workerman 在 Windows 上不支持一个文件声明多个 worker
* 因此这里采用官方推荐的「单 worker + onWorkerStart 动态 listen()」方案,
* 两个端口跑在同一个进程内,共享在线连接表。
* - 部署到 Linux 后此写法同样适用,可额外增加 Worker::count 多进程。
*/
use Workerman\Worker;
use Workerman\Protocols\Http\Request;
require_once __DIR__ . '/../vendor/autoload.php';
// 当前在线前端连接connection id => connection
$clients = [];
// ---------- 主 workerWebSocket 服务(员工前端连接) ----------
$wsWorker = new Worker('websocket://0.0.0.0:2346');
$wsWorker->name = 'ticket-broadcast';
$wsWorker->onConnect = function ($connection) use (&$clients) {
$connection->cid = spl_object_id($connection);
$clients[$connection->cid] = $connection;
echo '[' . date('Y-m-d H:i:s') . "] client connected, online: " . count($clients) . PHP_EOL;
};
$wsWorker->onClose = function ($connection) use (&$clients) {
if (isset($clients[$connection->cid])) {
unset($clients[$connection->cid]);
}
echo '[' . date('Y-m-d H:i:s') . "] client closed, online: " . count($clients) . PHP_EOL;
};
// ---------- 进程启动后追加 HTTP 触发端口(仅本机) ----------
$wsWorker->onWorkerStart = function ($worker) use (&$clients) {
$httpWorker = new Worker('http://127.0.0.1:2347');
$httpWorker->name = 'ticket-push-trigger';
$httpWorker->onMessage = function ($connection, Request $request) use (&$clients) {
if ($request->method() === 'POST' && $request->path() === '/push') {
$data = json_decode($request->rawBody(), true) ?: [];
$payload = json_encode([
'type' => 'ticket_changed',
'action' => isset($data['action']) ? (string)$data['action'] : 'update',
'ticket_id' => isset($data['ticket_id']) ? (int)$data['ticket_id'] : null,
], JSON_UNESCAPED_UNICODE);
$sent = 0;
foreach ($clients as $client) {
$client->send($payload);
$sent++;
}
echo '[' . date('Y-m-d H:i:s') . "] push [{$payload}] to {$sent} clients" . PHP_EOL;
$connection->send('ok');
} else {
$connection->send('bad request');
}
};
// 同一进程内动态监听第二端口Windows 下多 worker 的限制的官方解法)
$httpWorker->listen();
};
Worker::runAll();