wip
This commit is contained in:
@@ -5,6 +5,7 @@ namespace app\admin\controller;
|
||||
|
||||
use app\BaseController;
|
||||
use app\common\model\Tickets;
|
||||
use app\common\service\TicketNotifier;
|
||||
use app\common\service\TicketService;
|
||||
use think\facade\Session;
|
||||
|
||||
@@ -109,8 +110,14 @@ class Ticket extends BaseController
|
||||
$ticket = Tickets::find($id);
|
||||
if (!$ticket) return $this->error('工单不存在');
|
||||
|
||||
return $ticket->delete()
|
||||
? $this->success('删除成功', [])
|
||||
: $this->error('删除失败', []);
|
||||
$deleted = $ticket->delete();
|
||||
if (!$deleted) {
|
||||
return $this->error('删除失败', []);
|
||||
}
|
||||
|
||||
// 通知前端实时刷新(静默,广播服务不可用不影响删除流程)
|
||||
TicketNotifier::notify($id, 'delete');
|
||||
|
||||
return $this->success('删除成功', []);
|
||||
}
|
||||
}
|
||||
|
||||
50
app/common/service/TicketNotifier.php
Normal file
50
app/common/service/TicketNotifier.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,10 @@ class TicketService
|
||||
|
||||
// 当前无相同网站
|
||||
$ticketResult = Tickets::create($data);
|
||||
|
||||
// 通知前端实时刷新(静默,广播服务不可用不影响主流程)
|
||||
TicketNotifier::notify($ticketResult->id, 'create');
|
||||
|
||||
return $ticketResult;
|
||||
} catch (\Exception $e) {
|
||||
Cache::delete($lockKey);
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
"topthink/framework": "^8.0",
|
||||
"topthink/think-orm": "^3.0|^4.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": {
|
||||
"topthink/think-dumper": "^1.0",
|
||||
|
||||
79
worker/websocket.php
Normal file
79
worker/websocket.php
Normal 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 = [];
|
||||
|
||||
// ---------- 主 worker:WebSocket 服务(员工前端连接) ----------
|
||||
$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();
|
||||
Reference in New Issue
Block a user