From 92b4faf61f636222faabda3ca4bfbadcf7f9858a Mon Sep 17 00:00:00 2001 From: root Date: Tue, 7 Jul 2026 14:56:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=8B=E5=8A=A8=E5=85=B3=E9=97=AD=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E4=B8=8D=E8=A6=81=E8=87=AA=E5=8A=A8=E6=89=93=E5=BC=80?= =?UTF-8?q?=E9=87=8D=E5=90=AF=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/command/Install/split_ticket.sql | 1 + .../Install/split_ticket_manual_manage.sql | 6 ++++ application/admin/controller/split/Ticket.php | 17 ++++++++++- .../common/service/SplitTicketRuleService.php | 30 ++++++++++++++++++- .../SplitTicketSyncDispatchService.php | 13 +++++--- .../common/service/SplitTicketSyncService.php | 23 +++++++++++++- .../admin/command/Install/split_ticket.sql | 1 + .../Install/split_ticket_manual_manage.sql | 6 ++++ .../admin/controller/split/Ticket.php | 17 ++++++++++- .../common/service/SplitTicketRuleService.php | 30 ++++++++++++++++++- .../SplitTicketSyncDispatchService.php | 13 +++++--- .../common/service/SplitTicketSyncService.php | 23 +++++++++++++- 12 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 application/admin/command/Install/split_ticket_manual_manage.sql create mode 100644 patches/application/admin/command/Install/split_ticket_manual_manage.sql diff --git a/application/admin/command/Install/split_ticket.sql b/application/admin/command/Install/split_ticket.sql index 1ab349f..e0677c6 100755 --- a/application/admin/command/Install/split_ticket.sql +++ b/application/admin/command/Install/split_ticket.sql @@ -20,6 +20,7 @@ CREATE TABLE `fa_split_ticket` ( `account` varchar(50) NOT NULL DEFAULT '' COMMENT '工单账号', `password` varchar(50) NOT NULL DEFAULT '' COMMENT '工单密码', `status` enum('normal','hidden') NOT NULL DEFAULT 'normal' COMMENT '状态:normal=正常,hidden=停用', + `manual_manage` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '手动管理:0=否,1=是(手动关闭后暂停同步且禁止自动开启)', `complete_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '完成数量(同步)', `inbound_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '进线人数(同步)', `speed_per_hour` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '当前速度:每小时进线(同步)', diff --git a/application/admin/command/Install/split_ticket_manual_manage.sql b/application/admin/command/Install/split_ticket_manual_manage.sql new file mode 100644 index 0000000..abdec7a --- /dev/null +++ b/application/admin/command/Install/split_ticket_manual_manage.sql @@ -0,0 +1,6 @@ +-- 工单手动管理:手动关闭后禁止自动同步与自动开启 +SET NAMES utf8mb4; + +ALTER TABLE `fa_split_ticket` + ADD COLUMN `manual_manage` tinyint(1) unsigned NOT NULL DEFAULT 0 + COMMENT '手动管理:0=否,1=是(手动关闭后暂停同步且禁止自动开启)' AFTER `status`; diff --git a/application/admin/controller/split/Ticket.php b/application/admin/controller/split/Ticket.php index 66d181e..7317e41 100755 --- a/application/admin/controller/split/Ticket.php +++ b/application/admin/controller/split/Ticket.php @@ -279,11 +279,22 @@ class Ticket extends Backend $params['sync_fail_count'], $params['speed_snapshot_count'], $params['speed_snapshot_time'], - $params['click_count'] + $params['click_count'], + $params['manual_manage'] ); return $params; } + /** + * 用户切换工单状态时写入 manual_manage,防止同步自动恢复开启 + * + * @param array $values + */ + private function applyManualManageForStatusChange(array &$values): void + { + (new SplitTicketRuleService())->applyManualManageForStatusChange($values); + } + /** * 手动同步选中工单(投递 CLI 后台执行,Web 请求立即返回) */ @@ -408,6 +419,7 @@ class Ticket extends Backend $ruleService = new SplitTicketRuleService(); if (isset($values['status'])) { + $this->applyManualManageForStatusChange($values); $pk = $this->model->getPk(); $adminIds = $this->getDataLimitAdminIds(); $query = $this->model->where($pk, 'in', $ids); @@ -582,6 +594,9 @@ class Ticket extends Backend $params['number_type_custom'] = ''; } $oldStatus = (string) ($row['status'] ?? 'hidden'); + if (isset($params['status']) && (string) $params['status'] !== $oldStatus) { + $this->applyManualManageForStatusChange($params); + } $result = false; Db::startTrans(); try { diff --git a/application/common/service/SplitTicketRuleService.php b/application/common/service/SplitTicketRuleService.php index 3af1b97..4e8c44f 100755 --- a/application/common/service/SplitTicketRuleService.php +++ b/application/common/service/SplitTicketRuleService.php @@ -19,6 +19,31 @@ class SplitTicketRuleService $this->ratioDeferredService = $ratioDeferredService ?? new SplitNumberRatioDeferredService(); } + /** + * 手动关闭的工单(manual_manage=1):禁止自动同步改写 status,须用户手动开启 + */ + public function isManuallyClosed(Ticket $ticket): bool + { + return (int) ($ticket['manual_manage'] ?? 0) === 1; + } + + /** + * 列表/编辑切换状态时同步 manual_manage 标记 + * + * @param array $values + */ + public function applyManualManageForStatusChange(array &$values): void + { + if (!isset($values['status'])) { + return; + } + if ((string) $values['status'] === 'hidden') { + $values['manual_manage'] = 1; + } elseif ((string) $values['status'] === 'normal') { + $values['manual_manage'] = 0; + } + } + /** * 同步后应用工单开关规则;号码开关由 applyNumberRules 综合裁决(平台在线 + 单号上限 + 下号比率) */ @@ -190,10 +215,13 @@ class SplitTicketRuleService } /** - * 完成量、时间窗口决定工单开关(定时与手动同步均适用) + * 完成量、时间窗口决定工单开关(定时与手动同步均适用;手动关闭工单除外) */ public function applyTicketStatusRules(Ticket $ticket, int $completeCount): void { + if ($this->isManuallyClosed($ticket)) { + return; + } $status = $this->resolveTicketStatus($ticket, $completeCount); if ($status !== (string) ($ticket['status'] ?? 'hidden')) { Ticket::where('id', (int) $ticket['id'])->update([ diff --git a/application/common/service/SplitTicketSyncDispatchService.php b/application/common/service/SplitTicketSyncDispatchService.php index 71faa83..161d3ca 100755 --- a/application/common/service/SplitTicketSyncDispatchService.php +++ b/application/common/service/SplitTicketSyncDispatchService.php @@ -38,6 +38,10 @@ class SplitTicketSyncDispatchService foreach ($metaList as $meta) { $ticketId = (int) $meta['id']; + if ((int) ($meta['manual_manage'] ?? 0) === 1) { + $skipped[] = $ticketId; + continue; + } if ($lockService->isLocked($ticketId)) { $skipped[] = $ticketId; continue; @@ -326,15 +330,16 @@ class SplitTicketSyncDispatchService $rows = Db::name('split_ticket') ->where('id', 'in', $ids) - ->field('id,ticket_type,ticket_url') + ->field('id,ticket_type,ticket_url,manual_manage') ->select(); $list = []; foreach ($rows as $row) { $list[] = [ - 'id' => (int) $row['id'], - 'ticket_type' => (string) ($row['ticket_type'] ?? ''), - 'ticket_url' => (string) ($row['ticket_url'] ?? ''), + 'id' => (int) $row['id'], + 'ticket_type' => (string) ($row['ticket_type'] ?? ''), + 'ticket_url' => (string) ($row['ticket_url'] ?? ''), + 'manual_manage' => (int) ($row['manual_manage'] ?? 0), ]; } diff --git a/application/common/service/SplitTicketSyncService.php b/application/common/service/SplitTicketSyncService.php index d37c257..87ae830 100755 --- a/application/common/service/SplitTicketSyncService.php +++ b/application/common/service/SplitTicketSyncService.php @@ -51,12 +51,19 @@ class SplitTicketSyncService 'force' => $force, 'syncMode' => $syncMode, 'status' => (string) $ticket['status'], + 'manualManage' => (int) ($ticket['manual_manage'] ?? 0), 'syncFailCount' => (int) ($ticket['sync_fail_count'] ?? 0), 'syncTime' => (int) ($ticket['sync_time'] ?? 0), 'pageUrl' => (string) $ticket['ticket_url'], 'nodeHost' => SplitSyncConfigService::getNodeHost(), ]); + if ($this->ruleService->isManuallyClosed($ticket)) { + SplitTicketSyncLogger::log('sync', 'skipped', ['reason' => '工单已手动关闭']); + SplitTicketSyncLogger::clearTicketContext(); + return ['success' => false, 'message' => '工单已手动关闭,暂停同步', 'skipped' => true]; + } + if (!$force && !$dueValidated) { $skip = $this->shouldSkip($ticket); if ($skip !== null) { @@ -389,7 +396,9 @@ class SplitTicketSyncService return []; } - $query = Ticket::where('status', 'normal')->whereIn('ticket_type', $types); + $query = Ticket::where('status', 'normal') + ->where('manual_manage', 0) + ->whereIn('ticket_type', $types); if ($failThreshold > 0) { $query->where('sync_fail_count', '<', $failThreshold); } @@ -503,6 +512,15 @@ class SplitTicketSyncService */ private function doSync(Ticket $ticket, string $syncMode): array { + $freshTicket = Ticket::get((int) $ticket['id']); + if (!$freshTicket) { + return ['success' => false, 'message' => '工单不存在', 'skipped' => true]; + } + if ($this->ruleService->isManuallyClosed($freshTicket)) { + return ['success' => false, 'message' => '工单已手动关闭,暂停同步', 'skipped' => true]; + } + $ticket = $freshTicket; + $ticketType = (string) $ticket['ticket_type']; $pageUrl = trim((string) $ticket['ticket_url']); if ($pageUrl === '') { @@ -589,6 +607,9 @@ class SplitTicketSyncService private function shouldSkip(Ticket $ticket): ?string { + if ($this->ruleService->isManuallyClosed($ticket)) { + return '工单已手动关闭,暂停同步'; + } if ((string) $ticket['status'] === 'hidden') { return '工单已关闭'; } diff --git a/patches/application/admin/command/Install/split_ticket.sql b/patches/application/admin/command/Install/split_ticket.sql index 1ab349f..e0677c6 100755 --- a/patches/application/admin/command/Install/split_ticket.sql +++ b/patches/application/admin/command/Install/split_ticket.sql @@ -20,6 +20,7 @@ CREATE TABLE `fa_split_ticket` ( `account` varchar(50) NOT NULL DEFAULT '' COMMENT '工单账号', `password` varchar(50) NOT NULL DEFAULT '' COMMENT '工单密码', `status` enum('normal','hidden') NOT NULL DEFAULT 'normal' COMMENT '状态:normal=正常,hidden=停用', + `manual_manage` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '手动管理:0=否,1=是(手动关闭后暂停同步且禁止自动开启)', `complete_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '完成数量(同步)', `inbound_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '进线人数(同步)', `speed_per_hour` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT '当前速度:每小时进线(同步)', diff --git a/patches/application/admin/command/Install/split_ticket_manual_manage.sql b/patches/application/admin/command/Install/split_ticket_manual_manage.sql new file mode 100644 index 0000000..abdec7a --- /dev/null +++ b/patches/application/admin/command/Install/split_ticket_manual_manage.sql @@ -0,0 +1,6 @@ +-- 工单手动管理:手动关闭后禁止自动同步与自动开启 +SET NAMES utf8mb4; + +ALTER TABLE `fa_split_ticket` + ADD COLUMN `manual_manage` tinyint(1) unsigned NOT NULL DEFAULT 0 + COMMENT '手动管理:0=否,1=是(手动关闭后暂停同步且禁止自动开启)' AFTER `status`; diff --git a/patches/application/admin/controller/split/Ticket.php b/patches/application/admin/controller/split/Ticket.php index 66d181e..7317e41 100755 --- a/patches/application/admin/controller/split/Ticket.php +++ b/patches/application/admin/controller/split/Ticket.php @@ -279,11 +279,22 @@ class Ticket extends Backend $params['sync_fail_count'], $params['speed_snapshot_count'], $params['speed_snapshot_time'], - $params['click_count'] + $params['click_count'], + $params['manual_manage'] ); return $params; } + /** + * 用户切换工单状态时写入 manual_manage,防止同步自动恢复开启 + * + * @param array $values + */ + private function applyManualManageForStatusChange(array &$values): void + { + (new SplitTicketRuleService())->applyManualManageForStatusChange($values); + } + /** * 手动同步选中工单(投递 CLI 后台执行,Web 请求立即返回) */ @@ -408,6 +419,7 @@ class Ticket extends Backend $ruleService = new SplitTicketRuleService(); if (isset($values['status'])) { + $this->applyManualManageForStatusChange($values); $pk = $this->model->getPk(); $adminIds = $this->getDataLimitAdminIds(); $query = $this->model->where($pk, 'in', $ids); @@ -582,6 +594,9 @@ class Ticket extends Backend $params['number_type_custom'] = ''; } $oldStatus = (string) ($row['status'] ?? 'hidden'); + if (isset($params['status']) && (string) $params['status'] !== $oldStatus) { + $this->applyManualManageForStatusChange($params); + } $result = false; Db::startTrans(); try { diff --git a/patches/application/common/service/SplitTicketRuleService.php b/patches/application/common/service/SplitTicketRuleService.php index 3af1b97..4e8c44f 100755 --- a/patches/application/common/service/SplitTicketRuleService.php +++ b/patches/application/common/service/SplitTicketRuleService.php @@ -19,6 +19,31 @@ class SplitTicketRuleService $this->ratioDeferredService = $ratioDeferredService ?? new SplitNumberRatioDeferredService(); } + /** + * 手动关闭的工单(manual_manage=1):禁止自动同步改写 status,须用户手动开启 + */ + public function isManuallyClosed(Ticket $ticket): bool + { + return (int) ($ticket['manual_manage'] ?? 0) === 1; + } + + /** + * 列表/编辑切换状态时同步 manual_manage 标记 + * + * @param array $values + */ + public function applyManualManageForStatusChange(array &$values): void + { + if (!isset($values['status'])) { + return; + } + if ((string) $values['status'] === 'hidden') { + $values['manual_manage'] = 1; + } elseif ((string) $values['status'] === 'normal') { + $values['manual_manage'] = 0; + } + } + /** * 同步后应用工单开关规则;号码开关由 applyNumberRules 综合裁决(平台在线 + 单号上限 + 下号比率) */ @@ -190,10 +215,13 @@ class SplitTicketRuleService } /** - * 完成量、时间窗口决定工单开关(定时与手动同步均适用) + * 完成量、时间窗口决定工单开关(定时与手动同步均适用;手动关闭工单除外) */ public function applyTicketStatusRules(Ticket $ticket, int $completeCount): void { + if ($this->isManuallyClosed($ticket)) { + return; + } $status = $this->resolveTicketStatus($ticket, $completeCount); if ($status !== (string) ($ticket['status'] ?? 'hidden')) { Ticket::where('id', (int) $ticket['id'])->update([ diff --git a/patches/application/common/service/SplitTicketSyncDispatchService.php b/patches/application/common/service/SplitTicketSyncDispatchService.php index c39d351..4957d14 100755 --- a/patches/application/common/service/SplitTicketSyncDispatchService.php +++ b/patches/application/common/service/SplitTicketSyncDispatchService.php @@ -38,6 +38,10 @@ class SplitTicketSyncDispatchService foreach ($metaList as $meta) { $ticketId = (int) $meta['id']; + if ((int) ($meta['manual_manage'] ?? 0) === 1) { + $skipped[] = $ticketId; + continue; + } if ($lockService->isLocked($ticketId)) { $skipped[] = $ticketId; continue; @@ -326,15 +330,16 @@ class SplitTicketSyncDispatchService $rows = Db::name('split_ticket') ->where('id', 'in', $ids) - ->field('id,ticket_type,ticket_url') + ->field('id,ticket_type,ticket_url,manual_manage') ->select(); $list = []; foreach ($rows as $row) { $list[] = [ - 'id' => (int) $row['id'], - 'ticket_type' => (string) ($row['ticket_type'] ?? ''), - 'ticket_url' => (string) ($row['ticket_url'] ?? ''), + 'id' => (int) $row['id'], + 'ticket_type' => (string) ($row['ticket_type'] ?? ''), + 'ticket_url' => (string) ($row['ticket_url'] ?? ''), + 'manual_manage' => (int) ($row['manual_manage'] ?? 0), ]; } diff --git a/patches/application/common/service/SplitTicketSyncService.php b/patches/application/common/service/SplitTicketSyncService.php index d37c257..87ae830 100755 --- a/patches/application/common/service/SplitTicketSyncService.php +++ b/patches/application/common/service/SplitTicketSyncService.php @@ -51,12 +51,19 @@ class SplitTicketSyncService 'force' => $force, 'syncMode' => $syncMode, 'status' => (string) $ticket['status'], + 'manualManage' => (int) ($ticket['manual_manage'] ?? 0), 'syncFailCount' => (int) ($ticket['sync_fail_count'] ?? 0), 'syncTime' => (int) ($ticket['sync_time'] ?? 0), 'pageUrl' => (string) $ticket['ticket_url'], 'nodeHost' => SplitSyncConfigService::getNodeHost(), ]); + if ($this->ruleService->isManuallyClosed($ticket)) { + SplitTicketSyncLogger::log('sync', 'skipped', ['reason' => '工单已手动关闭']); + SplitTicketSyncLogger::clearTicketContext(); + return ['success' => false, 'message' => '工单已手动关闭,暂停同步', 'skipped' => true]; + } + if (!$force && !$dueValidated) { $skip = $this->shouldSkip($ticket); if ($skip !== null) { @@ -389,7 +396,9 @@ class SplitTicketSyncService return []; } - $query = Ticket::where('status', 'normal')->whereIn('ticket_type', $types); + $query = Ticket::where('status', 'normal') + ->where('manual_manage', 0) + ->whereIn('ticket_type', $types); if ($failThreshold > 0) { $query->where('sync_fail_count', '<', $failThreshold); } @@ -503,6 +512,15 @@ class SplitTicketSyncService */ private function doSync(Ticket $ticket, string $syncMode): array { + $freshTicket = Ticket::get((int) $ticket['id']); + if (!$freshTicket) { + return ['success' => false, 'message' => '工单不存在', 'skipped' => true]; + } + if ($this->ruleService->isManuallyClosed($freshTicket)) { + return ['success' => false, 'message' => '工单已手动关闭,暂停同步', 'skipped' => true]; + } + $ticket = $freshTicket; + $ticketType = (string) $ticket['ticket_type']; $pageUrl = trim((string) $ticket['ticket_url']); if ($pageUrl === '') { @@ -589,6 +607,9 @@ class SplitTicketSyncService private function shouldSkip(Ticket $ticket): ?string { + if ($this->ruleService->isManuallyClosed($ticket)) { + return '工单已手动关闭,暂停同步'; + } if ((string) $ticket['status'] === 'hidden') { return '工单已关闭'; }