issue-ai/CLAUDE.md

261 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md — issue.tlyq.ai 工单跟踪系统
## 项目概述
issue-ai 是基于 Next.js + SQLite 的工单跟踪管理系统部署在腾讯云txjp 服务器),域名 `issue.tlyq.ai`。与 assets.tlyq.ai 资产管理系统联动,获取设备信息、提供故障历史查询。
---
## 快速参考
| 属性 | 值 |
|------|-----|
| 站点域名 | `issue.tlyq.ai` |
| 服务器 | txjpIP: 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 <key>`。每个环境独立创建,互不通用。
**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
└── webnetexternal ← 共享网络
```
部署:`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=<assets-ai 的 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')`
---
## 故障排查
### 容器启动失败
```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"
```