245 lines
10 KiB
Markdown
245 lines
10 KiB
Markdown
# 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` |
|
||
|
||
### 常用命令
|
||
|
||
```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 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);② 未命中则查 `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
|
||
└── webnet(external) ← 共享网络
|
||
```
|
||
|
||
部署:`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"
|
||
```
|