13 KiB
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 |
常用命令
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 示例
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/ 来自镜像构建时):
ssh txjp "cd /root/docker/issue-ai && docker compose build --no-cache && docker compose down && docker compose up -d"
生产环境变量
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(北京时间)。两处必须遵守:
- 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')} - SQLite:所有
datetime('now')必须写成datetime('now', '+8 hours'),包括 CREATE TABLE 的 DEFAULT 值、UPDATE/SET 语句、以及查询条件中的时间比较。禁止使用不含时区偏移的datetime('now')。
- JavaScript/TypeScript:禁止使用
Git Tag 规范
使用日期版本号 vYYYY.MM.DD(如 v2026.05.18)。提交后打 tag 再推送:
git tag v$(date +%Y.%m.%d) && git push origin main && git push origin v$(date +%Y.%m.%d)
同一天多次提交只打一个 tag。详见根目录 CLAUDE.md。
故障排查
容器启动失败
ssh txjp "docker logs issue-ai"
月报/周报生成失败
先查日志:ssh txjp "docker logs issue-ai 2>&1 | grep -A5 'Report.*failed' | tail -30"
常见根因:
-
ENOENT: no such file or directory, open '/app/node_modules/xxx'— 新增了 npm 依赖但未重建镜像。修复:重建镜像(命令见上方 Docker 部署)。 -
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)。 -
fs.readFileSync依赖在 standalone 中丢失 —@vercel/nft不追踪动态读取。预防:next.config.ts中outputFileTracingIncludes已添加 echarts。
其他
# 数据库初始化
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"