10 KiB
CLAUDE.md — assets.tlyq.ai 资产管理系统
项目概述
assets-ai 是基于 Next.js + SQLite 的 IT 设备资产管理系统(CMDB),部署在腾讯云(txjp 服务器),域名 assets.tlyq.ai。管理 GPU 服务器、存储服务器等 120+ 台设备的完整硬件信息,通过 REST API 对外服务,供 issue.tlyq.ai 等系统调用。
快速参考
| 属性 | 值 |
|---|---|
| 站点域名 | assets.tlyq.ai |
| 服务器 | txjp(IP: 43.133.38.210) |
| 代码路径 | /root/docker/assets-ai/ |
| 本地端口 | 6177 |
| 容器名 | assets-ai |
| 数据库 | SQLite:data/assets.db |
| 默认账号 | admin / admin123 |
常用命令
cd /Users/niuniu/programs/docker/assets-ai
npm run dev # 本地开发
npm run build # 生产构建
npm run db:init # 初始化数据库
npm run import # 导入设备数据
关键文件
| 文件 | 职责 |
|---|---|
next.config.ts |
output: 'standalone',better-sqlite3 服务端打包 |
Dockerfile |
两阶段构建(alpine builder + alpine runner) |
docker-compose.yml |
使用 external webnet,挂载 .next 从宿主机 |
src/middleware.ts |
全局路由守卫(Cookie JWT / Bearer API Key 双模式) |
src/lib/db.ts |
SQLite 连接(WAL 模式,外键开启) |
src/lib/db-schema.ts |
表初始化 + 默认数据(admin/editor/viewer) |
src/lib/auth.ts |
JWT 签名/验证、API Key 生成/校验(自实现,未用 jsonwebtoken 包) |
src/lib/permissions.ts |
权限检查(按角色 JSON 匹配) |
src/lib/issue-client.ts |
调用 issue API 获取设备历史工单 |
src/lib/excel.ts |
Excel 模板生成 + 导入解析 |
src/types/index.ts |
User / Asset / ApiKey / PaginatedResult 等类型 |
src/app/api/assets/ |
资产 CRUD + 批量修改 + 导入/导出 + 高级查询 |
数据库 Schema
表概览
| 表名 | 说明 |
|---|---|
users |
用户账号(username/password_hash/role) |
roles |
角色定义(name/display_name/permissions JSON) |
sessions |
会话(JWT → user_id) |
api_keys |
API Key(SHA-256 hash,供外部系统调用) |
assets |
设备资产主体(68 列硬件字段) |
audit_logs |
审计日志 |
assets 表核心字段
设备标识:serial_number(UNIQUE)、device_type(GPU服务器/存储服务器)、device_purpose、room、rack_position、node_name、business_ip、hdm_ip、manufacturer、device_model、status
CPU/内存:cpu_model、cpu_generation、cpu_cores、cpu_count、cpu_threads、memory_model、memory_frequency、memory_unit_capacity、memory_count、memory_total
GPU/网络:gpu_model、gpu_power、gpu_count、NIC×3(nic1/2/3_model/type/speed/count)
存储/电源:sys_disk_*、data_disk1/2_*、raid_model/spec/count、psu1/2_model/power/count
预置角色
| 角色 | 权限 | 说明 |
|---|---|---|
admin |
["*"] |
全部权限 |
editor |
["assets:read","assets:write","assets:delete"] |
可增删改资产,不可管理用户和 Key |
viewer |
["assets:read"] |
只读 |
API 路由
认证
登录逻辑(v2.1):LDAP 优先 + 本地密码缓存回退 + localadmin 应急用户。
登录成功签发两个 cookie:session_assets(本地 JWT,24h)+ tlyq_session(共享 JWT,7 天,domain=.tlyq.ai)。
中间件优先检查 tlyq_session,回退 session_assets。getSession() 每次验证时检查 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/assets |
资产列表(分页/搜索/高级筛选/sort) |
| POST | /api/assets |
创建设备 |
| GET | /api/assets/[id] |
设备详情 |
| PUT | /api/assets/[id] |
更新设备 |
| DELETE | /api/assets/[id] |
删除设备 |
| POST | /api/assets/batch |
批量修改(仅允许部分字段,见下方限制) |
| POST | /api/assets/import |
Excel 模板导入(按 SN 匹配新增/更新) |
| GET | /api/assets/export |
导出 Excel |
| GET | /api/assets/field-values |
获取字段可选值(筛选下拉用) |
GET /api/assets 高级查询:
| 参数 | 说明 |
|---|---|
search |
全文搜索(SN、节点名、IP、型号、厂商) |
filter_<field> |
精确筛选(可多次使用实现 IN 筛选) |
filters |
JSON 数组:[{field, op, value}],op 支持 contains/equals/starts_with/ends_with/not_empty/empty |
sortKey / sortOrder |
排序字段 |
统计 / API Key / 用户
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/stats |
资产概览(总数/按状态/按类型/按厂商/按机房/保修预警) |
| GET/POST | /api/api-keys |
Key 列表 / 创建(仅显示一次) |
| DELETE | /api/api-keys/[id] |
删除 Key |
| GET/POST | /api/users |
用户列表/创建 |
| GET/PUT/DELETE | /api/users/[id] |
单个用户操作 |
认证机制
- Web UI(v2.1):
middleware.ts优先检查tlyq_session(共享 JWT,OA 统一签发)→ 回退session_assets(本地 JWT)。getSession()每次请求时检查 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,支持last_used_at追踪和 permissions 控制)。外部系统调用本系统 API 时,key 必须注册在ALLOWED_API_KEYS中
环境配置
本地与云端差异
| 环境变量 | 本地开发 | 云服务器(txjp) | 说明 |
|---|---|---|---|
ISSUE_API_URL |
http://localhost:6176/api |
http://issue-ai:3000/api |
调用 issue API 地址 |
ISSUE_API_KEY |
本地 issue-ai 生成 | 云上 issue-ai 生成 | 每个环境独立,不可跨环境使用 |
NEXT_PUBLIC_ISSUE_URL |
http://localhost:6176/tickets |
https://issue.tlyq.ai |
前端跳转链接(构建时内嵌) |
ALLOWED_API_KEYS |
issue-ai 调用本系统时需要的 Key | 云上 issue-ai 生成的 Key | 仅 issue→assets 方向需要 |
JWT_SECRET |
dev-secret-key-local |
${ASSETS_JWT_SECRET} |
生产必须强密钥 |
DATABASE_PATH |
./data/assets.db |
/app/data/assets.db |
Docker volume 挂载 |
| Cookie 名 | session_assets |
session_assets |
本地两系统用不同名防 localhost 域冲突 |
.env.local 示例
DATABASE_PATH=./data/assets.db
JWT_SECRET=dev-secret-key-local
NODE_ENV=development
ISSUE_API_URL=http://localhost:6176/api
ISSUE_API_KEY=ak_<32字节十六进制>
NEXT_PUBLIC_ISSUE_URL=http://localhost:6176/tickets
与 issue.tlyq.ai 的联动
assets-ai ──→ GET {ISSUE_API_URL}/tickets/by-asset?ip=xxx (Authorization: Bearer {ISSUE_API_KEY})
issue-ai ──→ GET {ASSETS_API_URL}/assets?search=IP (Authorization: Bearer {ASSETS_API_KEY})
- assets → issue(
src/lib/issue-client.ts):资产详情页展示历史工单,优先 ISSUE_API_KEY,回退用户 Cookie - API Key 配置:在 issue-ai
/settings/api-keys创建 Key → 写入 assets-ai 的ISSUE_API_KEY
批量编辑限制
POST /api/assets/batch 仅允许修改以下字段(安全考虑):
device_type, device_purpose, room, rack_position, status,
manufacturer, device_model, warranty_date
IP、序列号、硬件配置等必须通过单台编辑接口逐一修改。
Docker 部署
txjp 服务器
├── assets-ai(容器) ← Next.js standalone,监听 3000
├── nginx-ai ← 反向代理 assets.tlyq.ai → assets-ai:3000
└── webnet(external) ← 共享网络
部署:bash deploy-ai.sh → 选择 5。源码打包上传 → 服务器 npm install + npm run build → .next 挂载进容器生效。--force 强制重建,--restart 仅重启。
生产环境变量
DATABASE_PATH=/app/data/assets.db
JWT_SECRET=<线上密钥>
NODE_ENV=production
NEXT_PUBLIC_ISSUE_URL=https://issue.tlyq.ai/tickets
NEXT_PUBLIC_ISSUE_URL由deploy-ai.sh在构建前自动写入服务器.env。
开发规范
- 新增 API:在
src/app/api/下创建路由 → 顶部调用initDatabase()→getCurrentUser()验证 - 新增页面:在
src/app/(app)/下创建 → 布局由(app)/layout.tsx提供 - 日期处理(时区规范):整个系统统一使用 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 assets-ai"
# 数据库初始化
ssh txjp "docker exec assets-ai node -e \"require('./scripts/init-db.js')\""
# 重建镜像(不常用,.next 挂载已覆盖日常更新)
ssh txjp "cd /root/docker/assets-ai && docker compose down && docker compose build --no-cache && docker compose up -d"