assets-ai/CLAUDE.md

245 lines
10 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 — assets.tlyq.ai 资产管理系统
## 项目概述
assets-ai 是基于 Next.js + SQLite 的 IT 设备资产管理系统CMDB部署在腾讯云txjp 服务器),域名 `assets.tlyq.ai`。管理 GPU 服务器、存储服务器等 120+ 台设备的完整硬件信息,通过 REST API 对外服务,供 issue.tlyq.ai 等系统调用。
---
## 快速参考
| 属性 | 值 |
|------|-----|
| 站点域名 | `assets.tlyq.ai` |
| 服务器 | txjpIP: 43.133.38.210 |
| 代码路径 | `/root/docker/assets-ai/` |
| 本地端口 | 6177 |
| 容器名 | `assets-ai` |
| 数据库 | SQLite`data/assets.db` |
| 默认账号 | `admin` / `admin123` |
### 常用命令
```bash
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 KeySHA-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.1LDAP 优先 + 本地密码缓存回退 + localadmin 应急用户。
登录成功签发两个 cookie`session_assets`(本地 JWT24h+ `tlyq_session`(共享 JWT7 天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 UIv2.1**`middleware.ts` 优先检查 `tlyq_session`(共享 JWTOA 统一签发)→ 回退 `session_assets`(本地 JWT。`getSession()` 每次请求时检查 LLDAP 用户是否存在,已删除则清除 cookie 踢出
- **localadmin**:纯本地 BCrypt 认证,不依赖 LLDAP用于 LLDAP 故障时应急登录DB 预置admin 角色)
- **API Keyv2.2**`middleware.ts` 采用两级验证:① `ALLOWED_API_KEYS` 环境变量快速匹配(逗号分隔明文 key② 未命中则查 `api_keys` 数据库表SHA-256 hash。无效 key 在中间件层直接返回 401此前仅检查 `Bearer ak_` 前缀就放行,实际验证延后到 route handler。外部系统如 issue-ai调用本系统 API 时key 可注册在 `ALLOWED_API_KEYS``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` 示例
```bash
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`
详见 [issue-ai CLAUDE.md](../issue-ai/CLAUDE.md#与-assetstlyqai-的联动)。
---
## 批量编辑限制
`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
└── webnetexternal ← 共享网络
```
部署:`bash deploy-ai.sh` → 选择 5。源码打包上传 → 服务器 `npm install` + `npm run build``.next` 挂载进容器生效。`--force` 强制重建,`--restart` 仅重启。
### 生产环境变量
```bash
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北京时间。两处必须遵守
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')`
---
## 故障排查
```bash
# 容器日志
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"
```