# CLAUDE.md — monitor.tlyq.ai 告警监控中心 ## 项目概述 monitor-ai 是独立部署的统一告警监控中心,监控 tlyq.ai 基础设施所有容器(Authelia、Redis、LLDAP、OA、assets、issue、Gitea、Nginx、www、cloud、token)的健康状态,通过企业微信 Webhook 推送告警。部署在腾讯云(txjp 服务器),域名 `monitor.tlyq.ai`。 --- ## 快速参考 | 属性 | 值 | |------|-----| | 站点域名 | `monitor.tlyq.ai` | | 服务器 | txjp(IP: 43.133.38.210) | | 代码路径 | `/root/docker/monitor-ai/` | | 本地端口 | 6181 | | 容器名 | `monitor-ai` | | 数据库 | SQLite:`data/monitor.db` | | 默认账号 | `localadmin` / 首次部署自动生成 | ### 常用命令 ```bash cd /Users/niuniu/programs/docker/monitor-ai npm run dev # 本地开发(http://localhost:6181) npm run build # 生产构建 npm run start # 启动生产服务 ``` --- ## 技术栈 | 技术 | 版本 | 用途 | |------|------|------| | Next.js | 15 App Router | Web 框架(standalone 输出) | | SQLite | 3.x | 数据库(通过 `execFileSync` + `sqlite3` CLI) | | Tailwind CSS | v4 | 样式(`@tailwindcss/postcss`) | | lucide-react | ^1.8.0 | 图标库 | | ldapts | ^6.0.0 | LDAP 认证客户端 | | openid-client | ^5.7.1 | OIDC 认证 | | tsx | ^4.0.0 | Worker TypeScript 执行 | --- ## 目录结构 ``` monitor-ai/ ├── Dockerfile # 两阶段构建(node:22-alpine) ├── docker-compose.yml # 容器编排(webnet external) ├── entrypoint.sh # Docker 入口(双进程守护:Worker + Server) ├── next.config.ts # output: standalone, transpilePackages ├── shared -> ../shared # 共享库符号链接 ├── scripts/ │ ├── monitor-worker.ts # Worker 独立入口 │ ├── deploy-monitor.sh # 部署脚本 │ └── monitor-watchdog.sh # 健康检查兜底脚本 ├── src/ │ ├── middleware.ts # Edge Runtime 路由守卫 │ ├── app/ │ │ ├── page.tsx # 仪表盘(四层:标题 + 状态条 + KPI + 服务卡片) │ │ ├── login/page.tsx # 登录页 │ │ ├── services/page.tsx # 服务管理 │ │ ├── settings/page.tsx # 告警设置 │ │ ├── alerts/page.tsx # 告警历史 │ │ ├── status-history/page.tsx # 状态变更历史 │ │ ├── admin/ # 管理页面(users/roles/audit-logs) │ │ └── api/ # API 路由 │ ├── components/ │ │ ├── Sidebar.tsx # 侧边栏导航 │ │ ├── ThemeProvider.tsx # 主题上下文 │ │ └── ThemeToggle.tsx # 主题切换 │ └── lib/ │ ├── config.ts # 配置常量 │ ├── db.ts # 数据库操作封装(execFileSync + escapeSql) │ ├── auth-config.ts # 认证配置 │ ├── permissions.ts # RBAC 权限定义 │ ├── audit.ts # 审计日志封装 │ └── monitor-worker.ts # MonitorWorker 核心类 └── data/ └── monitor.db # SQLite 数据库 ``` --- ## 关键文件 | 文件 | 职责 | |------|------| | `src/lib/monitor-worker.ts` | MonitorWorker 核心类(30s tick,健康检查 + 状态管理 + 告警推送) | | `src/lib/db.ts` | SQLite 操作封装(execFileSync + escapeSql,无 ORM) | | `src/lib/permissions.ts` | RBAC 权限定义(11 个权限点,admin/editor/viewer/localadmin) | | `src/lib/audit.ts` | 审计日志封装(调用 `@shared/lib/audit`) | | `src/lib/auth-config.ts` | 认证配置(OIDC + LDAP) | | `src/middleware.ts` | Edge Runtime 路由守卫(Cookie JWT 验证,公开/管理路径控制) | | `scripts/monitor-worker.ts` | Worker 独立入口(信号处理、优雅退出) | | `entrypoint.sh` | Docker 入口(双进程守护:Worker + Server,任一退出则容器退出) | | `shared/lib/alert/` | 告警引擎(AlertManager、HealthChecker、类型定义) | | `shared/lib/wechat/` | 企业微信推送(WeChatPusher、消息格式化) | --- ## 数据库 Schema ### 表概览 | 表名 | 说明 | 来源 | |------|------|------| | `services` | 被监控服务列表 | `@shared/lib/db/alert-schema` | | `alert_channels` | Webhook 告警渠道配置 | `@shared/lib/db/alert-schema` | | `alert_settings` | 全局设置 + 冷却状态持久化 | `@shared/lib/db/alert-schema` | | `status_history` | 服务状态变更事件 | `@shared/lib/db/alert-schema` | | `alert_history` | 告警推送记录 | `@shared/lib/db/alert-schema` | | `users` | 用户账号 | `src/lib/db.ts`(内联) | | `permissions` | 权限定义 | `src/lib/db.ts`(内联) | | `role_permissions` | 角色-权限映射 | `src/lib/db.ts`(内联) | | `audit_logs` | 审计日志 | `@shared/lib/audit/audit-schema` | ### services 表 | 字段 | 类型 | 说明 | |------|------|------| | `id` | INTEGER PK | 主键 | | `name` | TEXT | 服务名称 | | `category` | TEXT | 分类(endpoint / container / custom) | | `alert_level` | TEXT | 告警级别(critical / warning / info) | | `checks` | TEXT | 检查配置(JSON 数组,HealthCheckConfig[]) | | `check_interval` | INTEGER | 检查间隔秒数(默认 30) | | `check_timeout` | INTEGER | 检查超时秒数(默认 10) | | `enabled` | INTEGER | 是否启用(默认 1) | | `current_status` | TEXT | 当前状态(normal / abnormal / unknown) | | `status_since` | TEXT | 状态持续起始时间 | | `display_order` | INTEGER | 显示排序 | ### alert_channels 表 | 字段 | 类型 | 说明 | |------|------|------| | `id` | INTEGER PK | 主键 | | `name` | TEXT | 渠道名称 | | `channel_type` | TEXT | 渠道类型(默认 'wechat') | | `webhook_url` | TEXT | Webhook 地址 | | `enabled` | INTEGER | 是否启用 | | `level_critical` | INTEGER | 是否接收 critical 告警 | | `level_warning` | INTEGER | 是否接收 warning 告警 | | `level_info` | INTEGER | 是否接收 info 告警 | | `quiet_enabled` | INTEGER | 是否启用免打扰 | | `quiet_start` | TEXT | 免打扰开始时间 | | `quiet_end` | TEXT | 免打扰结束时间 | | `quiet_bypass_critical` | INTEGER | critical 是否绕过免打扰 | | `cooldown_minutes` | INTEGER | 冷却时间(分钟) | ### status_history 表 | 字段 | 类型 | 说明 | |------|------|------| | `id` | INTEGER PK | 主键 | | `service_id` | INTEGER FK | 关联服务 | | `from_status` | TEXT | 原状态 | | `to_status` | TEXT | 新状态 | | `started_at` | TEXT | 状态开始时间 | | `ended_at` | TEXT | 状态结束时间 | | `duration_seconds` | INTEGER | 持续时长 | | `error_message` | TEXT | 错误信息 | | `failed_checks` | TEXT | 失败检查详情(JSON) | | `truncated_by_restart` | INTEGER | 是否被重启截断 | ### alert_history 表 | 字段 | 类型 | 说明 | |------|------|------| | `id` | INTEGER PK | 主键 | | `channel_id` | INTEGER FK | 告警渠道 | | `service_id` | INTEGER FK | 关联服务 | | `status_event_id` | INTEGER FK | 关联状态事件 | | `alert_type` | TEXT | 告警类型(alert / recovery / flapping) | | `level` | TEXT | 告警级别 | | `title` | TEXT | 标题 | | `content` | TEXT | 内容 | | `sent_at` | TEXT | 发送时间 | | `success` | INTEGER | 是否成功 | | `response_code` | INTEGER | HTTP 响应码 | ### users 表 | 字段 | 类型 | 说明 | |------|------|------| | `id` | INTEGER PK | 主键 | | `username` | TEXT UNIQUE | 用户名 | | `display_name` | TEXT | 显示名 | | `email` | TEXT | 邮箱 | | `role` | TEXT | 角色(admin / editor / viewer) | | `password_hash` | TEXT | 密码哈希 | | `is_active` | INTEGER | 是否启用 | 默认用户:`localadmin`(admin 角色,BCrypt 密码哈希) ### 预置角色 | 角色 | 权限 | 说明 | |------|------|------| | `admin` | 全部 11 个权限 | 管理员 | | `editor` | dashboard:view, services:view, alerts:view, status_history:view, status_history:stats | 编辑者 | | `viewer` | dashboard:view, services:view, status_history:view | 只读 | | `localadmin` | 硬编码绕过(hasPermission 始终返回 true) | 应急管理员 | ### 权限列表 `dashboard:view`、`services:view`、`services:manage`、`alerts:view`、`alerts:manage`、`status_history:view`、`status_history:stats`、`audit:view`、`users:view`、`users:manage`、`roles:manage` --- ## API 路由 ### 公开 API | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/health` | 自身健康检查(返回 `{status:'OK'}`) | ### 认证 登录逻辑:OIDC SSO 优先 + LDAP 本地登录 + localadmin 应急用户。 登录成功签发 `tlyq_session` cookie(共享 JWT,domain=.tlyq.ai)。 中间件验证 JWT payload(Edge Runtime,不解密签名,仅解码 payload 检查过期时间)。 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/auth/login/oidc` | OIDC SSO 重定向到 Authelia | | POST | `/api/auth/login` | LDAP 或 localadmin 登录 | | GET | `/api/auth/callback` | OIDC 回调处理 | | POST | `/api/auth/logout` | 登出(清除 cookie,重定向 Authelia end_session) | | GET | `/api/auth/me` | 当前用户信息 | ### 服务管理 | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | GET | `/api/services` | 登录 | 服务列表(含解析后的 JSON checks) | | POST | `/api/services` | services:manage | 新增服务 | | PUT | `/api/services/[id]` | services:manage | 更新服务 | | DELETE | `/api/services/[id]` | services:manage | 删除服务 | | POST | `/api/services/[id]/check` | services:manage | 手动触发单次健康检查 | ### 告警渠道 | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | GET | `/api/alert-channels` | alerts:manage | 渠道列表(webhook_url 脱敏) | | POST | `/api/alert-channels` | alerts:manage | 新增渠道 | | PUT | `/api/alert-channels/[id]` | alerts:manage | 更新渠道 | | DELETE | `/api/alert-channels/[id]` | alerts:manage | 删除渠道 | | POST | `/api/alert-channels/[id]/test` | alerts:manage | 测试推送 | ### 告警历史 / 状态历史 | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | GET | `/api/alerts` | alerts:view | 告警历史(分页) | | GET | `/api/status-history` | 登录 | 状态变更历史(分页,可按 service_id 筛选) | | GET | `/api/status` | 登录 | 所有服务实时状态汇总 | ### 管理 | 方法 | 路径 | 权限 | 说明 | |------|------|------|------| | GET | `/api/admin/worker-status` | dashboard:view | Worker 运行状态 | --- ## Worker 架构 ### MonitorWorker 核心类 位置:`src/lib/monitor-worker.ts` ``` MonitorWorker ├── HealthChecker(@shared/lib/alert/health-checker) │ ├── HttpChecker — HTTP 状态码 + 响应体匹配 │ └── DockerChecker — docker ps 容器状态检查 ├── AlertManager(@shared/lib/alert/alert-manager) │ ├── evaluate() — 三阶段告警决策(级别匹配 → 免打扰 → 冷却) │ └── CooldownProvider — alert_settings 表持久化冷却状态 └── WeChatPusher(@shared/lib/wechat/wechat-pusher) └── pushMarkdown() — 企业微信 Webhook 推送 ``` ### 运行机制 | 阶段 | 间隔 | 说明 | |------|------|------| | 健康检查 | 30 秒 tick | 遍历所有 enabled 服务,并发执行检查(Promise.allSettled) | | 抖动检测 | 10 分钟窗口 | 同一服务 10 分钟内状态切换 >= 5 次 → 触发 flapping 告警,抑制后续告警 | | 持续异常提醒 | 30 分钟 | 服务持续 abnormal 状态,每 30 分钟发送提醒 | | 恢复通知 | 即时 | 服务恢复正常时发送 recovery 通知,绕过免打扰和冷却 | | WAL 检查点 | 1 小时 | 被动 WAL checkpoint(PASSIVE) | | 数据清理 | 每天 | 删除 180 天前的 status_history 和 alert_history(每批 LIMIT 1000) | ### Docker 入口(entrypoint.sh) ``` entrypoint.sh ├── npx tsx scripts/monitor-worker.ts & # 后台启动 Worker ├── node server.js & # 后台启动 Next.js Server └── 守护循环:任一进程退出则容器退出 # 保证双进程存活 └── trap SIGTERM/SIGINT → 清理退出 ``` --- ## 认证机制 - **OIDC SSO**:通过 Authelia 进行 PKCE 授权码流程,登录成功后签发 `tlyq_session` 共享 JWT cookie - **LDAP 本地登录**:通过 LLDAP 进行 bind 认证,签发 `tlyq_session` cookie - **localadmin**:纯本地 BCrypt 认证,不依赖 LLDAP/OIDC,用于应急登录 - **Edge Runtime 中间件**:`src/middleware.ts` 运行在 Edge Runtime,使用 `atob` 解码 JWT payload 检查过期时间,不验证签名(签名验证在 route handler 层) --- ## 环境配置 ### 本地与云端差异 | 环境变量 | 本地开发 | 云服务器(txjp) | 说明 | |---------|---------|----------------|------| | `DATABASE_PATH` | `./data/monitor.db` | `/app/data/monitor.db` | Docker volume 挂载 | | `MONITOR_MODE` | `dev` | `local` | 运行模式 | | `AUTHELIA_URL` | `https://127.0.0.1:6180` | `https://sso.tlyq.ai` | Authelia 地址 | | `OIDC_CLIENT_ID` | `monitor-oidc` | 同 | OIDC 客户端 ID | | `OIDC_CLIENT_SECRET` | 本地生成的值 | 服务器生成的值 | OIDC 客户端密钥(每个环境独立) | | `OIDC_REDIRECT_URI` | `http://localhost:6181/api/auth/callback` | `https://monitor.tlyq.ai/api/auth/callback` | OIDC 回调地址 | | `JWT_SECRET` | `dev-jwt-secret-change-in-production` | 部署脚本自动生成 | JWT 签名密钥 | | `COOKIE_DOMAIN` | `.tlyq.ai` | 同 | Cookie 域 | | `LDAP_URL` | `ldap://ldap-ai:3890` | `ldap://ldap-ai:3890` | LLDAP 地址(Docker 内网) | | `LOCALADMIN_PASSWORD` | `admin123` | 部署脚本自动生成 | 应急管理员密码 | | `NODE_TLS_REJECT_UNAUTHORIZED` | `0` | `0` | TLS 证书验证(Authelia 自签名) | ### `.env.local` 示例 ```bash DATABASE_PATH=./data/monitor.db MONITOR_MODE=dev AUTHELIA_URL=https://127.0.0.1:6180 OIDC_CLIENT_ID=monitor-oidc OIDC_CLIENT_SECRET=<本地生成的值> OIDC_REDIRECT_URI=http://localhost:6181/api/auth/callback JWT_SECRET=dev-jwt-secret-change-in-production COOKIE_DOMAIN=.tlyq.ai LDAP_URL=ldap://ldap-ai:3890 LOCALADMIN_PASSWORD=admin123 NODE_TLS_REJECT_UNAUTHORIZED=0 ``` --- ## 共享库引用 | 库 | 用途 | 导入路径 | |------|------|------| | `@shared/lib/auth/jwt` | JWT 签名/验证(零依赖,Node crypto) | `import { signJwt, verifyJwt } from '@shared/lib/auth/jwt'` | | `@shared/lib/auth/oidc` | OIDC PKCE 流程 | `import { discoverOidcConfig, buildAuthorizeUrl } from '@shared/lib/auth/oidc'` | | `@shared/lib/auth/ldap` | LDAP 认证 | `import { ldapAuth } from '@shared/lib/auth/ldap'` | | `@shared/lib/auth/middleware` | 路由守卫工厂 | `import { createMiddleware } from '@shared/lib/auth/middleware'` | | `@shared/lib/auth/user-sync` | OIDC 用户同步 | `import { syncOidcUser } from '@shared/lib/auth/user-sync'` | | `@shared/lib/alert/alert-manager` | 告警决策引擎 | `import { AlertManager } from '@shared/lib/alert/alert-manager'` | | `@shared/lib/alert/health-checker` | 健康检查引擎 | `import { HealthChecker, HttpChecker, DockerChecker } from '@shared/lib/alert/health-checker'` | | `@shared/lib/audit/write-audit-log` | 审计日志 | `import { writeAuditLog } from '@shared/lib/audit/write-audit-log'` | | `@shared/lib/wechat/wechat-pusher` | 企业微信推送 | `import { WeChatPusher } from '@shared/lib/wechat/wechat-pusher'` | | `@shared/lib/wechat/message-formatter` | 消息格式化 | `import { formatAlertLevel } from '@shared/lib/wechat/message-formatter'` | | `@shared/lib/db/alert-schema` | 数据库 Schema 常量 | `import { ALL_TABLES_SQL } from '@shared/lib/db/alert-schema'` | | `@shared/ui` | UI 组件库 | `import { Button, Card, Badge } from '@shared/ui'` | | `@shared/ui/login-page` | 统一登录页 | `import LoginPage from '@shared/ui/login-page'` | > **路径映射**:`tsconfig.json` 中配置 `"@shared/*": ["./shared/*"]`,`shared` 是指向 `../shared` 的符号链接。 --- ## 关键设计决策 ### execFileSync 数组参数 数据库操作使用 `execFileSync('sqlite3', [dbPath, '-json', sql])` 而非 ORM。SQL 通过数组参数传递,避免 shell 注入。所有用户输入通过 `escapeSql()` 函数转义(单引号双写)。 ### Edge Runtime 中间件 `src/middleware.ts` 运行在 Edge Runtime,不能使用 Node.js `crypto` 模块。JWT 验证仅解码 payload 检查过期时间,签名验证留给 route handler 层处理。 ### 双进程守护 `entrypoint.sh` 同时启动 Worker 和 Server,使用 bash 守护循环监控。任一进程退出则整个容器退出,由 Docker 重启策略接管。 ### 冷却状态持久化 告警冷却状态存储在 `alert_settings` 表中(key 格式:`cooldown:{channel_id}:{service_id}:{level}`),容器重启后冷却状态不丢失。 --- ## Docker 部署 ``` txjp 服务器 ├── monitor-ai(容器) ← Next.js standalone + Worker,监听 6181 ├── nginx-ai ← 反向代理 monitor.tlyq.ai → monitor-ai:6181 └── webnet(external) ← 共享网络 ``` 部署:`bash deploy-monitor.sh`(本地模式)或 `bash deploy-monitor.sh --remote --host `。 源码打包上传 → 服务器 `npm install` + `npm run build` → `.next` 挂载进容器生效。 ### 生产环境变量 ```bash DATABASE_PATH=/app/data/monitor.db MONITOR_MODE=local AUTHELIA_URL=https://sso.tlyq.ai OIDC_CLIENT_ID=monitor-oidc OIDC_CLIENT_SECRET=<部署脚本自动生成> OIDC_REDIRECT_URI=https://monitor.tlyq.ai/api/auth/callback JWT_SECRET=<部署脚本自动生成> COOKIE_DOMAIN=.tlyq.ai LDAP_URL=ldap://ldap-ai:3890 LOCALADMIN_PASSWORD=<部署脚本自动生成> NODE_ENV=production NODE_TLS_REJECT_UNAUTHORIZED=0 ``` --- ## 开发规范 - **新增 API**:在 `src/app/api/` 下创建路由 → 顶部调用 `initDatabase()` → `getCurrentUser()` 验证 → `checkPermission()` 校验 - **新增页面**:在 `src/app/` 下创建 → 布局由 `client-layout.tsx` 提供(Sidebar + ThemeProvider) - **权限格式**:`resource:action`,如 `checkPermission(user, 'services:manage')` - **审计日志**:所有写操作 API(POST/PUT/DELETE)必须添加审计日志: ```typescript import { writeAuditLog } from '@/lib/audit' writeAuditLog({ userId: user.id, action: 'create' | 'update' | 'delete', entityType: 'service' | 'alert_channel' | 'user', entityId: id, details: { ... }, ipAddress: getClientIP(request) }) ``` - **日期处理(时区规范)**:整个系统统一使用 UTC+8(北京时间)。必须遵守: 1. **JavaScript/TypeScript**:禁止使用 `Date.toISOString()` 格式化本地日期,应使用本地时间方法拼接 2. **SQLite**:所有 `datetime('now')` 必须写成 `datetime('now', '+8 hours')` 3. **INSERT 时间列**:数据库表 DEFAULT 已统一为 `datetime('now', '+8 hours')`,INSERT 时可省略 --- ## 健康检查兜底 `scripts/monitor-watchdog.sh` 是独立于 monitor-ai 的兜底脚本,通过 crontab 每 5 分钟执行: ```bash */5 * * * * /root/docker/monitor-ai/scripts/monitor-watchdog.sh >> /var/log/monitor-watchdog.log 2>&1 ``` 当 `/api/health` 连续失败时,直接通过企业微信 Webhook 发送告警,并尝试重启容器。 --- ## 故障排查 ```bash # 容器日志 ssh txjp "docker logs monitor-ai" # Worker 日志(实时) ssh txjp "docker logs -f monitor-ai 2>&1 | grep -i worker" # 数据库查看 ssh txjp "docker exec monitor-ai sqlite3 /app/data/monitor.db 'SELECT id, name, current_status FROM services;'" # 健康检查 ssh txjp "curl -s -k https://monitor.tlyq.ai/api/health" # 手动触发检查 ssh txjp "docker exec monitor-ai sqlite3 /app/data/monitor.db 'UPDATE services SET current_status=\"unknown\";'" # 重建镜像(新增依赖后) ssh txjp "cd /root/docker/monitor-ai && docker compose build --no-cache && docker compose down && docker compose up -d" ``` --- ## Git Tag 规范 使用日期版本号 `vYYYY.MM.DD`(如 `v2026.07.01`)。提交后打 tag 再推送: ```bash git tag v$(date +%Y.%m.%d) && git push origin main && git push origin v$(date +%Y.%m.%d) ``` 同一天多次提交只打一个 tag。详见根目录 `CLAUDE.md`。