# CLAUDE.md — issue.tlyq.ai 工单跟踪系统 ## 项目概述 issue-ai 是基于 Next.js + SQLite 的工单跟踪管理系统,部署在腾讯云(txjp 服务器),域名 `issue.tlyq.ai`。与 assets.tlyq.ai 资产管理系统联动,获取设备信息、提供故障历史查询。 --- ## 快速参考 | 属性 | 值 | |------|-----| | 站点域名 | `issue.tlyq.ai` | | 服务器 | txjp(IP: 43.133.38.210) | | 代码路径 | `/root/docker/issue-ai/` | | 本地端口 | 5176 | | 容器名 | `issue-ai` | | 数据库 | SQLite:`data/issue.db` | | 报告存储 | `reports/` 目录(环境变量 `REPORTS_DIR` 可覆盖) | | 默认账号 | `admin` / `admin123` | ### 常用命令 ```bash cd /Users/niuniu/programs/docker/issue-ai npm run dev # 本地开发 npm run build # 生产构建 npm run db:init # 初始化数据库 npm run import # 导入工单 ``` --- ## 关键文件 | 文件 | 职责 | |------|------| | `next.config.ts` | `output: 'standalone'`,`outputFileTracingIncludes` 防止 fs.readFileSync 依赖丢失 | | `Dockerfile` | 两阶段构建(alpine builder + debian slim runner),含 Chromium 系统依赖 | | `docker-compose.yml` | 使用 external webnet,挂载 `.next` 从宿主机 | | `src/lib/db.ts` | SQLite 连接(单例,WAL 模式,外键开启) | | `src/lib/db-schema.ts` | 表初始化 + 默认数据(admin/operator/viewer) | | `src/lib/auth.ts` | JWT 解析、session 验证 | | `src/lib/permissions.ts` | 权限检查(admin 全能,按角色 JSON 匹配) | | `src/lib/sla.ts` | SLA 超时计算(Tier 1 = 1% 自然月秒数) | | `src/lib/assets-client.ts` | 调用 assets API 获取设备信息 | | `src/lib/report-generator.ts` | 报告数据组装 | | `src/lib/docx-export.ts` | Word 文档生成 | | `src/lib/excel.ts` | Excel 解析与导出 | | `src/lib/pdf.ts` | PDF 生成(puppeteer) | | `src/types/ticket.ts` | TicketCreateInput / TicketUpdateInput | | `src/types/report.ts` | ReportType / ReportData | --- ## 数据库 Schema ### 表概览 | 表名 | 说明 | |------|------| | `users` | 用户账号(username/password_hash/role) | | `roles` | 角色定义(name/display_name/permissions JSON) | | `sessions` | 会话(JWT id → user_id) | | `tickets` | 工单主体 | | `ticket_steps` | 工单时间线(关联 ticket_id) | | `reports` | 报告记录 | | `audit_logs` | 审计日志 | ### tickets 表核心字段 | 字段 | 类型 | 说明 | |------|------|------| | `ticket_no` | TEXT UNIQUE | 工单编号 | | `device_ip` | TEXT | 设备 IP(用于调 assets API 补全信息) | | `ticket_type` | TEXT | 工单类型(OEM诊断/OEM维修) | | `fault_category` | TEXT | 故障大类(硬件故障/网络故障/误判/其他/空) | | `fault_subcategory` | TEXT | 故障子分类 | | `current_status` | TEXT | 状态(open/in_progress/resolved/closed) | | `availability` | REAL | 服务可用性(0-1) | | `counted_in_sla` | INTEGER | 是否计入 SLA | | `assign_time` | TEXT | 指派时间 | | `close_time` | TEXT | 关闭时间 | | `duration_minutes` | INTEGER | 处理时长(分钟) | | `conclusion` | TEXT | 结论 | | `parts_replaced` | TEXT | 更换配件 | ### 预置角色 | 角色 | 权限 | |------|------| | `admin` | `["*"]`(全部权限) | | `operator` | `["tickets:read","tickets:write","reports:read"]` | | `viewer` | `["tickets:read","reports:read"]` | --- ## API 路由 ### 认证 | 方法 | 路径 | 说明 | |------|------|------| | POST | `/api/auth/login` | 登录(username + password → JWT cookie) | | POST | `/api/auth/logout` | 登出 | | GET | `/api/auth/me` | 当前用户信息 | ### 工单 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/tickets` | 工单列表(分页/筛选/搜索) | | POST | `/api/tickets` | 创建工单(Cookie 认证) | | POST | `/api/tickets/external` | 外部系统创建工单(API Key 认证,幂等) | | GET | `/api/tickets/[id]` | 工单详情(自动调 assets API 补全设备名) | | PUT | `/api/tickets/[id]` | 更新工单(含时间线 steps) | | DELETE | `/api/tickets/[id]` | 删除工单 | | POST | `/api/tickets/import` | Excel 批量导入 | ### 统计 / 报告 / 用户 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/stats` | 工单概览统计 | | GET | `/api/stats/monthly` | 月度趋势 | | GET | `/api/stats/sla` | SLA 达标率 | | GET/POST | `/api/reports` | 报告列表 / 生成 | | GET | `/api/reports/[id]` | 报告详情/下载 | | GET/POST | `/api/users` | 用户列表/创建 | | GET/PUT/DELETE | `/api/users/[id]` | 单个用户操作 | | GET/POST | `/api/roles` | 角色列表/创建 | | GET/PUT/DELETE | `/api/roles/[id]` | 单个角色操作 | --- ## 环境配置 ### 本地与云端差异 | 环境变量 | 本地开发 | 云服务器(txjp) | 说明 | |---------|---------|----------------|------| | `ASSETS_API_URL` | `http://localhost:5177/api` | `http://assets-ai:3000/api` | 调用 assets API 地址 | | `ASSETS_API_KEY` | 本地 assets-ai 生成 | 云上 assets-ai 生成 | **每个环境独立,不可跨环境使用** | | `NEXT_PUBLIC_ASSETS_URL` | `http://localhost:5177` | `https://assets.tlyq.ai` | 前端跳转链接(构建时内嵌) | | `ALLOWED_API_KEYS` | 本地 issue-ai 生成的 Key | 云上 issue-ai 生成的 Key | 允许外部系统调本系统的 Key,逗号分隔 | | `JWT_SECRET` | `dev-secret-key-local` | `${ISSUE_JWT_SECRET}` | 本地两系统需一致(同 localhost 域) | | `DATABASE_PATH` | `./data/issue.db` | `/app/data/issue.db` | Docker volume 挂载 | | Cookie 名 | `session_issue` | `session_issue` | 本地用不同名防 localhost 域冲突 | ### `.env.local` 示例 ```bash DATABASE_PATH=./data/issue.db JWT_SECRET=dev-secret-key-local ADMIN_PASSWORD=admin123 NODE_ENV=development ASSETS_API_URL=http://localhost:5177/api ASSETS_API_KEY=ak_<32字节十六进制> ALLOWED_API_KEYS=ak_<32字节十六进制> NEXT_PUBLIC_ASSETS_URL=http://localhost:5177 ``` --- ## 与 assets.tlyq.ai 的联动 ``` issue-ai ──→ GET {ASSETS_API_URL}/assets?search=IP&pageSize=50 (Authorization: Bearer {ASSETS_API_KEY}) assets-ai ──→ GET {ISSUE_API_URL}/tickets/by-asset?ip=xxx (Authorization: Bearer {ISSUE_API_KEY}) ``` - **issue → assets**(`src/lib/assets-client.ts`):根据 device_ip 模糊搜索,匹配 business_ip 或 hdm_ip - **assets → issue**(`src/app/api/tickets/by-asset/route.ts`):查询同 IP 历史工单,支持 Cookie 和 API Key 双认证 ### API Key 创建与配置 Key 格式:`ak_<32位十六进制>`,认证头:`Authorization: Bearer `。每个环境独立创建,互不通用。 **issue → assets 方向**:在 assets-ai `/settings/api-keys` 创建 Key → 写入 issue-ai 的 `ASSETS_API_KEY` **assets → issue 方向**:在 issue-ai `/settings/api-keys` 创建 Key → 写入 assets-ai 的 `ISSUE_API_KEY` + issue-ai 的 `ALLOWED_API_KEYS` --- ## Docker 部署 ``` txjp 服务器 ├── issue-ai(容器) ← Next.js standalone,监听 3000 ├── nginx-ai ← 反向代理 issue.tlyq.ai → issue-ai:3000 └── webnet(external) ← 共享网络 ``` 部署:`bash deploy-ai.sh` → 选择 4。源码打包上传 → 服务器 `npm install` + `npm run build` → `.next` 挂载进容器生效。`--force` 强制重建,`--restart` 仅重启。 **关键**:新增 npm 依赖后必须重建 Docker 镜像(`deploy-ai.sh` 只在宿主机 `npm install`,容器内 `/app/node_modules/` 来自镜像构建时): ```bash ssh txjp "cd /root/docker/issue-ai && docker compose build --no-cache && docker compose down && docker compose up -d" ``` ### 生产环境变量 ```bash DATABASE_PATH=/app/data/issue.db JWT_SECRET=<线上密钥> ASSETS_API_URL=http://assets-ai:3000/api ASSETS_API_KEY= NODE_ENV=production NEXT_PUBLIC_ASSETS_URL=https://assets.tlyq.ai ``` --- ## 仪表盘统计规则 - **整体服务可用性**:`AVG(tickets.availability) × 100`(保留 2 位小数) - **故障分类**:硬件故障 / 网络故障 / 误判 / 其他(content 含 agent+上报) / 空值 - **批量更新**:`UPDATE tickets SET fault_category='其他' WHERE (content LIKE '%agent%' OR content LIKE '%上报%') AND (fault_category IS NULL OR fault_category = '');` --- ## 开发规范 - **新增 API**:在 `src/app/api/` 下创建路由 → 顶部调用 `initDatabase()` → `getCurrentUser()` 验证 → `hasPermission()` 校验 - **新增页面**:在 `src/app/(app)/` 下创建 → 布局由 `(app)/layout.tsx` 提供 - **权限格式**:`resource:action`,如 `hasPermission(user, 'tickets:write')` - **日期处理**:禁止使用 `Date.toISOString()` 格式化本地日期。`toISOString()` 返回 UTC 时间,在中国时区(UTC+8)下 `new Date('2026-04-01T00:00:00').toISOString()` 会返回 `"2026-03-31T16:00:00.000Z"`,日期偏移一天。应使用本地时间方法拼接:`${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}` --- ## 故障排查 ### 容器启动失败 ```bash ssh txjp "docker logs issue-ai" ``` ### 月报/周报生成失败 先查日志:`ssh txjp "docker logs issue-ai 2>&1 | grep -A5 'Report.*failed' | tail -30"` 常见根因: 1. **`ENOENT: no such file or directory, open '/app/node_modules/xxx'`** — 新增了 npm 依赖但未重建镜像。修复:重建镜像(命令见上方 Docker 部署)。 2. **`error while loading shared libraries: libglib-2.0.so.0`** — 缺少 Chromium 系统库。Dockerfile 已安装全套依赖(libglib2.0-0、libnss3、libnspr4、libatk1.0-0、libatk-bridge2.0-0、libcups2、libdrm2、libdbus-1-3、libxkbcommon0、libxcomposite1、libxdamage1、libxfixes3、libxrandr2、libgbm1、libasound2、libpango-1.0-0、libcairo2)。 3. **`fs.readFileSync` 依赖在 standalone 中丢失** — `@vercel/nft` 不追踪动态读取。预防:`next.config.ts` 中 `outputFileTracingIncludes` 已添加 echarts。 ### 其他 ```bash # 数据库初始化 ssh txjp "docker exec issue-ai node -e \"require('./scripts/init-db.js')\"" # 确认 assets API 连通性 ssh txjp "docker exec issue-ai sh -c 'wget -q -O- http://assets-ai:3000/api/auth/me'" # 实时日志 ssh txjp "docker logs -f issue-ai" ```