dev #1
@@ -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('删除成功', []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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);
|
$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);
|
||||||
|
|||||||
@@ -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
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