293 lines
13 KiB
Markdown
293 lines
13 KiB
Markdown
# 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/` |
|
||
| 本地端口 | 6176 |
|
||
| 容器名 | `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 路由
|
||
|
||
### 认证
|
||
|
||
登录逻辑(v2.1):LDAP 优先 + 本地密码缓存回退 + localadmin 应急用户。
|
||
登录成功签发两个 cookie:`session_issue`(本地 JWT,7 天)+ `tlyq_session`(共享 JWT,7 天,domain=.tlyq.ai)。
|
||
中间件优先检查 `tlyq_session`,回退 `session_issue`。`getCurrentUser()` 每次验证时检查 LLDAP 用户是否存在(已删除则清除 cookie 踢出)。
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| POST | `/api/auth/login` | 登录(LDAP 优先 + 本地回退) |
|
||
| POST | `/api/auth/logout` | 登出(清除两个 cookie) |
|
||
| GET | `/api/auth/me` | 当前用户信息 |
|
||
| GET | `/api/internal/roles` | 内部 API:返回角色列表(x-internal-key 鉴权) |
|
||
|
||
### 工单
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| 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]` | 单个角色操作 |
|
||
|
||
---
|
||
|
||
## 认证机制
|
||
|
||
- **Web UI(v2.1)**:`middleware.ts` 优先检查 `tlyq_session`(共享 JWT,OA 统一签发)→ 回退 `session_issue`(本地 JWT)。`getCurrentUser()` 每次请求时检查 LLDAP 用户是否存在,已删除则清除 cookie 踢出
|
||
- **localadmin**:纯本地 BCrypt 认证,不依赖 LLDAP,用于 LLDAP 故障时应急登录(DB 预置,admin 角色)
|
||
- **API Key(v2.2)**:`middleware.ts` 检查 `ALLOWED_API_KEYS` 环境变量(逗号分隔明文 key),无效 key 在中间件层直接返回 401。注意:middleware 运行在 Edge Runtime,不能使用 `better-sqlite3`,DB 级别的 key 验证由 route handler 中的 `auth.ts verifyApiKey()` 进行(查 `api_keys` 表 SHA-256 hash)。外部系统调用本系统 API 时,key 必须注册在 `ALLOWED_API_KEYS` 中(Web UI 创建的 key 还需同步到环境变量,重启容器后生效)
|
||
|
||
---
|
||
|
||
## 环境配置
|
||
|
||
### 本地与云端差异
|
||
|
||
| 环境变量 | 本地开发 | 云服务器(txjp) | 说明 |
|
||
|---------|---------|----------------|------|
|
||
| `ASSETS_API_URL` | `http://localhost:6177/api` | `http://assets-ai:3000/api` | 调用 assets API 地址 |
|
||
| `ASSETS_API_KEY` | 本地 assets-ai 生成 | 云上 assets-ai 生成 | **每个环境独立,不可跨环境使用** |
|
||
| `NEXT_PUBLIC_ASSETS_URL` | `http://localhost:6177` | `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:6177/api
|
||
ASSETS_API_KEY=ak_<32字节十六进制>
|
||
ALLOWED_API_KEYS=ak_<32字节十六进制>
|
||
NEXT_PUBLIC_ASSETS_URL=http://localhost:6177
|
||
```
|
||
|
||
---
|
||
|
||
## 与 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>`。每个环境独立创建,互不通用。
|
||
|
||
**验证策略(v2.2)**:两边中间件均采用 `ALLOWED_API_KEYS` 环境变量 → `api_keys` 数据库表两级验证。Key 注册在任意一处即可通过认证,推荐走 Web UI 创建(写入 `api_keys` 表,支持权限和追踪)。
|
||
|
||
**issue → assets 方向**:在 assets-ai `/settings/api-keys` 创建 Key → 写入 issue-ai 的 `ASSETS_API_KEY`(`.env` 或 `docker-compose.yml` 环境变量)
|
||
|
||
**assets → issue 方向**:在 issue-ai `/settings/api-keys` 创建 Key → 写入 assets-ai 的 `ISSUE_API_KEY` + issue-ai 的 `ALLOWED_API_KEYS`
|
||
|
||
> **部署验证**:`deploy-ai.sh` 部署 issue 站点后会自动验证 issue→assets API 连通性(使用配置的 `ASSETS_API_KEY` 调 assets API),若返回 401 则部署失败退出,防止 API Key 配置遗漏上线。
|
||
|
||
---
|
||
|
||
## 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=<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')`
|
||
- **日期处理(时区规范)**:整个系统统一使用 UTC+8(北京时间)。两处必须遵守:
|
||
1. **JavaScript/TypeScript**:禁止使用 `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')}`
|
||
2. **SQLite**:所有 `datetime('now')` 必须写成 `datetime('now', '+8 hours')`,包括 CREATE TABLE 的 DEFAULT 值、UPDATE/SET 语句、以及查询条件中的时间比较。禁止使用不含时区偏移的 `datetime('now')`。
|
||
|
||
---
|
||
|
||
## Git Tag 规范
|
||
|
||
使用日期版本号 `vYYYY.MM.DD`(如 `v2026.05.18`)。提交后打 tag 再推送:
|
||
|
||
```bash
|
||
git tag v$(date +%Y.%m.%d) && git push origin main && git push origin v$(date +%Y.%m.%d)
|
||
```
|
||
|
||
同一天多次提交只打一个 tag。详见根目录 `CLAUDE.md`。
|
||
|
||
---
|
||
|
||
## 故障排查
|
||
|
||
### 容器启动失败
|
||
```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"
|
||
```
|