Files
Xboard/app/Services/TicketService.php
yinjianm c64badfc23 feat(admin-frontend): 补齐活跃筛选与支付快照能力
新增用户管理“活跃状态”高级筛选,并在后端支持
activity_status 复合规则,支持按活跃与非活跃筛选用户。

补齐订单支付成功快照落库与后台展示,保存支付渠道、
支付方法、实付金额和支付 IP,并在订单详情中优先展示。

同时增强节点页在线/离线筛选与批量删除、仪表盘快捷入口,
并修复已关闭工单再次回复后自动重开的统一语义。

附带同步测试、迁移、CI 工作流命名及知识库记录
2026-04-25 00:59:08 +08:00

124 lines
4.0 KiB
PHP

<?php
namespace App\Services;
use App\Exceptions\ApiException;
use App\Jobs\SendEmailJob;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Services\Plugin\HookManager;
class TicketService
{
public function reply($ticket, $message, $userId)
{
try {
DB::beginTransaction();
$ticketMessage = TicketMessage::create([
'user_id' => $userId,
'ticket_id' => $ticket->id,
'message' => $message
]);
$this->applyReplyState($ticket, (int) $userId);
if (!$ticketMessage || !$ticket->save()) {
throw new \Exception();
}
DB::commit();
return $ticketMessage;
} catch (\Exception $e) {
DB::rollback();
return false;
}
}
public function replyByAdmin($ticketId, $message, $userId): void
{
$ticket = Ticket::where('id', $ticketId)->first();
if (!$ticket) {
throw new ApiException('工单不存在');
}
$ticketMessage = $this->reply($ticket, $message, $userId);
if (!$ticketMessage) {
throw new ApiException('工单回复失败');
}
HookManager::call('ticket.reply.admin.after', [$ticket, $ticketMessage]);
$this->sendEmailNotify($ticket, $ticketMessage);
}
/**
* Applies the unified reply state to a ticket.
*
* @param Ticket $ticket The target ticket.
* @param int $userId The replying user ID.
* @return void
*/
private function applyReplyState(Ticket $ticket, int $userId): void
{
$isAdmin = $userId !== $ticket->user_id;
$ticket->status = Ticket::STATUS_OPENING;
$ticket->reply_status = $isAdmin
? Ticket::REPLY_STATUS_REPLIED
: Ticket::REPLY_STATUS_WAITING;
$ticket->last_reply_user_id = $userId;
}
public function createTicket($userId, $subject, $level, $message)
{
try {
DB::beginTransaction();
if (Ticket::where('status', 0)->where('user_id', $userId)->lockForUpdate()->first()) {
DB::rollBack();
throw new ApiException('存在未关闭的工单');
}
$ticket = Ticket::create([
'user_id' => $userId,
'subject' => $subject,
'level' => $level,
'reply_status' => Ticket::REPLY_STATUS_WAITING,
'last_reply_user_id' => $userId,
]);
if (!$ticket) {
throw new ApiException('工单创建失败');
}
$ticketMessage = TicketMessage::create([
'user_id' => $userId,
'ticket_id' => $ticket->id,
'message' => $message
]);
if (!$ticketMessage) {
DB::rollBack();
throw new ApiException('工单消息创建失败');
}
DB::commit();
return $ticket;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
// 半小时内不再重复通知
private function sendEmailNotify(Ticket $ticket, TicketMessage $ticketMessage)
{
$user = User::find($ticket->user_id);
$cacheKey = 'ticket_sendEmailNotify_' . $ticket->user_id;
if (!Cache::get($cacheKey)) {
Cache::put($cacheKey, 1, 1800);
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => '您在' . admin_setting('app_name', 'Notification Service') . '的工单得到了回复',
'template_name' => 'notify',
'template_value' => [
'name' => admin_setting('app_name', 'Notification Service'),
'url' => admin_setting('app_url'),
'content' => "主题:{$ticket->subject}\r\n回复内容:{$ticketMessage->message}"
]
]);
}
}
}