From d4c08a8cdff176b7501b261e669b7cb8acebe467 Mon Sep 17 00:00:00 2001 From: GiveMePast <1248298477@qq.com> Date: Sat, 8 Aug 2026 15:06:12 +0800 Subject: [PATCH] wip --- AGENTS.md | 54 +++++++++++++ app/admin/controller/Ticket.php | 6 +- app/common/service/TicketService.php | 114 ++++++++++++--------------- 3 files changed, 108 insertions(+), 66 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..951caad --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,54 @@ +# AGENTS.md — NewTicket 项目约束 + +## 技术栈(后端) + +- **语言/运行时**:PHP 8.0.2 NTS(phpStudy 自带,位于 `D:\phpstudy_pro\Extensions\php\php8.0.2nts`,已加入 PATH) +- **框架**:ThinkPHP 8(`topthink/framework ^8.0`),多应用模式(`auto_multi_app`),ORM 为 `topthink/think-orm ^3.0|^4.0` +- **Web 服务器**:小皮面板(phpStudy)Apache 2.4.39,托管 `localhost:80` +- **数据库**:小皮面板 MySQL 5.7.26(`127.0.0.1:3306`),库名 `tickets`,账号 `gds_tickets` / `gds_tickets`(见 `.env`) +- **实时推送**:Workerman v4.2.1(`workerman/workerman ^4.2`;v5 需 PHP 8.1,当前环境装不了),独立进程 `worker/websocket.php` +- **包管理**:Composer 2.5.8 +- **代码结构**:`app/`(PSR-4 自动加载)、`config/`、`route/`、`worker/`;前端为独立仓库 `vue_tickets`(Vue 3 + Vite,非本仓库) + +## 运行方式(重要) + +- **后端由小皮面板接管**:Apache 跑 80 端口、MySQL 跑 3306 端口。**不要启动 `php think run` 之类的旁路开发服务器**——端口、Session、环境不一致会掩盖问题,也会与面板托管的服务冲突。 +- 本地验证接口直接请求 `http://localhost/admin/`(如 `http://localhost/admin/ticketsfind`)。 +- admin 应用路由前缀为 `/admin`,路由定义在 `app/admin/route/app.php`。 +- WebSocket 推送是独立进程,需单独启动:`php worker/websocket.php start`;`TicketNotifier::notify` 设计为静默失败,广播服务不可用不影响主流程。 +- 数据库表名为复数(`users`、`companys`、`company_operators`、`tickets`),模型名对应单数。 + +## 开发约束 + +1. **运行方式**:后端一律由小皮面板托管(Apache + MySQL)。禁止启动 `php think run`、独立 php 内置服务器等旁路服务。 +2. **改动范围尽量小**: + - 只修改任务直接相关的代码,不做无关重构、不批量重命名、不调整格式、不移动文件。 + - 优先使用精确的小范围编辑(`patch`),避免大段重写。 + - 修改公共函数/接口时,检查所有调用方,确保参数与返回值一致。 +3. **每次修改后必须生成修改报告**:所有代码改动完成后,输出一份修改报告(模板见文末),包含修改内容、原因、影响范围、验证方式与结果、剩余风险。 +4. **数据库谨慎**:写入、并发、锁、事务保持谨慎;不直接操作生产数据库;需要改表结构时先说明迁移方案与回滚方式。 +5. **安全与配置**:不在代码中硬编码密钥/Token/生产配置;`.env` 与凭据文件不提交、不打印;接口改动时检查前后端字段、错误码、返回结构是否一致(前端在 `vue_tickets` 仓库)。 +6. **版本控制**:修改前检查 `git status`,不覆盖用户未提交的修改;不擅自 commit / push / 合并分支。 +7. **验证**:优先在本地真实环境(小皮面板托管服务)做实际验证,并展示真实命令输出;临时验证产生的数据、进程、脚本必须在结束后清理;无法运行时明确说明哪些结论仅为静态分析。 + +## 修改报告模板 + +每次改动完成后,按以下结构输出报告: + +``` +## 修改报告 + +### 修改内容 +- 涉及文件与函数: +- 具体改动点: + +### 修改原因 + +### 影响范围 +- 调用方/关联模块: + +### 验证方式与结果 +- 执行命令与实际输出(粘贴关键结果): + +### 剩余风险 / 注意事项 +``` diff --git a/app/admin/controller/Ticket.php b/app/admin/controller/Ticket.php index d892fbd..be9a123 100644 --- a/app/admin/controller/Ticket.php +++ b/app/admin/controller/Ticket.php @@ -28,7 +28,11 @@ class Ticket extends BaseController "sortOrder" => $this->request->param('sort_order', 'desc'), ]; - $result = $ticketService->getTicketList($params); + $page = max(($params['page'] ?? 1), 1); + $limit = min(max(($params['limit'] ?? 20), 1), 100); + $order = strtolower($params['sortOrder'] ?? 'desc') === 'asc' ? 'asc' : 'desc'; + + $result = $ticketService->queryTickets($params, $page, $limit, $order); return $this->success('查询成功', $result); } diff --git a/app/common/service/TicketService.php b/app/common/service/TicketService.php index 04a587b..c6b85c9 100644 --- a/app/common/service/TicketService.php +++ b/app/common/service/TicketService.php @@ -7,51 +7,51 @@ use app\common\model\CompanyOperators; use app\common\model\Companys; use app\common\model\User; -use think\facade\Cache; - class TicketService { /** * 获取工单列表业务逻辑 - * @param array $params 外部传入的过滤参数 + * @param array $params 过滤参数 + * @param int $page 页码 + * @param int $limit 每页数量 + * @param string $order 排序方式 */ - public function getTicketList(array $params) + public function queryTickets(array $params, int $page, int $limit, string $order) { - $page = max(intval($params['page'] ?? 1), 1); - $limit = min(max(intval($params['limit'] ?? 20), 1), 100); - $order = strtolower($params['sortOrder']) === 'asc' ? 'asc' : 'desc'; + try { + // 查询构造器 + $query = Tickets::with(['ticketStaff', 'ticketCompany', 'ticketOperator']); - // 查询构造器 - $query = Tickets::with(['ticketStaff', 'ticketCompany', 'ticketOperator']); - - $equalFilters = ['operator_id', 'user_id', 'status']; - foreach ($equalFilters as $field) { - if (!empty($params[$field])) { - $query->where($field, $params[$field]); + $equalFilters = ['operator_id', 'user_id', 'status']; + foreach ($equalFilters as $field) { + if (isset($params[$field]) && $params[$field] !== '') { + $query->where($field, $params[$field]); + } } + + if (!empty($params['searchKeyword'])) { // 模糊搜索(标题,备注) + $query->where(function ($q) use ($params) { + $q->where('website', 'like', '%' . $params['searchKeyword'] . '%') + ->whereOr('remark', 'like', '%' . $params['searchKeyword'] . '%'); + }); + } + + $list = $query->order('create_time', $order) + ->hidden(['ticketStaff', 'ticketCompany', 'ticketOperator', 'company_id', 'operator_id', 'user_id']) + ->append(['username', 'company_name', 'operator_name']) + ->paginate( + $limit, + false, + [ + 'page' => $page, + 'query' => $params + ] + ); + + return $list; + } catch (\Exception $e) { //数据库连接失败,表不存在 + throw new \Exception('查询工单列表失败: ' . $e->getMessage()); } - - // 关键词(模糊搜索) - if (!empty($params['searchKeyword'])) { - $query->where(function ($q) use ($params) { - $q->where('website', 'like', '%' . $params['searchKeyword'] . '%') - ->whereOr('remark', 'like', '%' . $params['searchKeyword'] . '%'); - }); - } - - $list = $query->order('create_time', $order) - ->hidden(['ticketStaff', 'ticketCompany', 'ticketOperator', 'company_id', 'operator_id', 'user_id']) - ->append(['username', 'company_name', 'operator_name']) - ->paginate( - $limit, - false, - [ - 'page' => $page, - 'query' => $params - ] - ); - - return $list; } @@ -59,45 +59,29 @@ class TicketService * 新增工单 * */ - public function createTicket($data) + public function createTicket(array $data) { - if (empty($data['website'])) { + if (isset($data['website']) && $data['website'] === '') { // isset检查键存在且不为null throw new \Exception('网站不能为空'); } $website = trim($data['website']); - // 并发锁 - $lockKey = 'create_ticket_lock_' . md5($website); + $unfinishedTicket = Tickets::where('website', $website) // 查找是否有未完成的同网站工单 + ->whereIn('status', [0, 1]) + ->find(); - if (Cache::has($lockKey)) { - throw new \Exception('当前该网址正在被创建工单中,请稍后重试'); + if ($unfinishedTicket) { + throw new \Exception('已存在未完成的同网站工单,无法创建'); } - Cache::set($lockKey, 1, 5); // 锁定5秒 + // 当前无相同网站 + $ticketResult = Tickets::create($data); - try { - // 查找是否有未完成的同网站工单 - $unfinishedTicket = Tickets::where('website', $website) - ->whereIn('status', [0, 1]) - ->find(); + // 通知前端实时刷新(静默,广播服务不可用不影响主流程) + TicketNotifier::notify($ticketResult->id, 'create'); - if ($unfinishedTicket) { - Cache::delete($lockKey); - throw new \Exception('已存在未完成的同网站工单,无法创建'); - } - - // 当前无相同网站 - $ticketResult = Tickets::create($data); - - // 通知前端实时刷新(静默,广播服务不可用不影响主流程) - TicketNotifier::notify($ticketResult->id, 'create'); - - return $ticketResult; - } catch (\Exception $e) { - Cache::delete($lockKey); - throw $e; - } + return $ticketResult; } @@ -109,7 +93,7 @@ class TicketService * @param $role 当前登录用户角色 * @return array */ - public function updateTicket($id, $data, $allowFields, $isRole, $isSuper = false) + public function updateTicket(int $id, array $data, array $allowFields, $isRole, $isSuper = false) { $ticket = Tickets::where('id', $id)->find();