424 lines
14 KiB
Markdown
424 lines
14 KiB
Markdown
# CLAUDE.md — shared 共享库
|
||
|
||
## 概述
|
||
|
||
shared 是 tlyq.ai 项目群的共享库,包含后端认证、告警、审计、企业微信推送等标准模块,以及前端 React UI 组件库。所有模块遵循零业务依赖、配置驱动、TypeScript 优先的设计原则。
|
||
|
||
---
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
shared/
|
||
├── lib/ # 后端服务端库(无 barrel,按路径直接导入)
|
||
│ ├── auth/ # 认证标准库
|
||
│ │ ├── types.ts # 类型定义(AuthConfig, SessionPayload, OidcUserinfo 等)
|
||
│ │ ├── jwt.ts # JWT 签名/验证(零依赖,Node crypto)
|
||
│ │ ├── ldap.ts # LDAP 认证(ldapts 客户端)
|
||
│ │ ├── oidc.ts # OIDC PKCE 流程(openid-client)
|
||
│ │ ├── middleware.ts # Next.js 路由守卫工厂
|
||
│ │ └── user-sync.ts # OIDC 用户同步到本地 SQLite
|
||
│ ├── alert/ # 告警引擎
|
||
│ │ ├── types.ts # 类型定义(AlertLevel, ServiceStatus, CheckResult 等)
|
||
│ │ ├── alert-manager.ts # AlertManager 告警决策引擎
|
||
│ │ └── health-checker.ts # HealthChecker 健康检查引擎
|
||
│ ├── audit/ # 审计日志
|
||
│ │ ├── audit-schema.ts # audit_logs 表 DDL
|
||
│ │ └── write-audit-log.ts # writeAuditLog 函数
|
||
│ ├── db/ # 数据库 Schema 定义
|
||
│ │ └── alert-schema.ts # 5 张表 DDL(services, alert_channels 等)
|
||
│ └── wechat/ # 企业微信 Webhook
|
||
│ ├── message-formatter.ts # 消息格式化
|
||
│ └── wechat-pusher.ts # WeChatPusher 推送客户端
|
||
└── ui/ # 前端 React 组件库(@tlyq/shared-ui)
|
||
├── package.json # 发布为 @tlyq/shared-ui v1.0.0
|
||
├── index.tsx # Barrel 导出(9 个组件)
|
||
├── Button.tsx # 按钮(4 种 variant,3 种 size,loading 状态)
|
||
├── Badge.tsx # 标签(5 种 variant)
|
||
├── Card.tsx # 卡片容器
|
||
├── Input.tsx # 输入框(含 label 和 error)
|
||
├── Select.tsx # 下拉选择
|
||
├── Modal.tsx # 模态框(backdrop 点击关闭,body scroll 锁定)
|
||
├── Table.tsx # 表格(styled thead,tbody 由调用方提供)
|
||
├── Toast.tsx # 通知条(右下角固定定位)
|
||
├── Pagination.tsx # 分页(中文标签:"上一页"/"下一页")
|
||
├── hooks/ # React Hooks
|
||
│ ├── useDirtyTracker.ts # 脏状态追踪("修改后才显示保存按钮"模式)
|
||
│ └── index.ts # Barrel 导出
|
||
└── login-page.tsx # 统一登录页(SSO + LDAP 切换,不在 barrel 中导出)
|
||
```
|
||
|
||
---
|
||
|
||
## 设计原则
|
||
|
||
### 零业务依赖
|
||
|
||
共享库不包含任何站点特定的业务逻辑。所有需要外部状态(数据库、配置)的模块通过 TypeScript 接口注入依赖,不耦合具体实现。
|
||
|
||
### 配置驱动
|
||
|
||
模块行为通过配置对象控制,而非硬编码。例如:
|
||
- `createMiddleware(config)` 通过 `AuthConfig` 配置公开路径、管理路径、cookie 名称
|
||
- `AlertManager` 通过 `CooldownProvider` 接口获取冷却状态
|
||
- `HealthChecker` 通过 `registerChecker()` 注册检查器
|
||
|
||
### TypeScript 优先
|
||
|
||
所有模块使用 TypeScript 编写,导出完整类型定义。消费方通过 `tsconfig.json` 的 `paths` 映射导入。
|
||
|
||
---
|
||
|
||
## 各模块详解
|
||
|
||
### lib/auth/ — 认证标准库
|
||
|
||
统一认证栈:OIDC SSO + LDAP 本地登录 + localadmin 应急用户 + 共享 JWT session。
|
||
|
||
#### types.ts
|
||
|
||
```typescript
|
||
interface AuthConfig {
|
||
jwtSecret: string
|
||
cookieDomain: string
|
||
cookieName?: string // 默认 'tlyq_session'
|
||
cookieMaxAge?: number // 默认 7 天(秒)
|
||
publicPaths?: string[] // 公开路径(不需要认证)
|
||
adminPaths?: string[] // 管理路径(需要 admin 角色)
|
||
ldapUrl?: string
|
||
ldapBaseDn?: string
|
||
autheliaUrl?: string
|
||
oidcClientId?: string
|
||
oidcClientSecret?: string
|
||
oidcRedirectUri?: string
|
||
}
|
||
|
||
interface SessionPayload {
|
||
sub: string // 用户 ID 或 username
|
||
username: string
|
||
role: string
|
||
exp: number
|
||
iat: number
|
||
}
|
||
|
||
interface OidcUserinfo {
|
||
sub: string
|
||
preferred_username: string
|
||
email?: string
|
||
name?: string
|
||
groups?: string[]
|
||
}
|
||
|
||
interface LdapAuthResult {
|
||
success: boolean
|
||
username?: string
|
||
displayName?: string
|
||
email?: string
|
||
isAdmin?: boolean
|
||
error?: string
|
||
}
|
||
```
|
||
|
||
#### jwt.ts
|
||
|
||
```typescript
|
||
signJwt(options: { payload: SessionPayload, secret: string, expiresIn?: number }): string
|
||
verifyJwt(token: string, secret: string): SessionPayload | null
|
||
sharedCookieConfig(domain: string, maxAge?: number): CookieConfig
|
||
```
|
||
|
||
零外部依赖,使用 Node.js `crypto` 模块实现 HS256 JWT 签名和验证。
|
||
|
||
#### ldap.ts
|
||
|
||
```typescript
|
||
ldapAuth(config: { url: string, baseDn: string }, username: string, password: string): Promise<LdapAuthResult>
|
||
checkAdminGroup(config: { url: string, baseDn: string }, username: string): Promise<boolean>
|
||
```
|
||
|
||
使用 `ldapts` 客户端进行 LDAP bind 认证和管理员组成员检查。
|
||
|
||
#### oidc.ts
|
||
|
||
```typescript
|
||
discoverOidcConfig(url: string): Promise<OidcConfig>
|
||
generatePkce(): { codeVerifier: string, codeChallenge: string }
|
||
generateState(): string
|
||
buildAuthorizeUrl(config: OidcConfig, params: { clientId: string, redirectUri: string, state: string, codeChallenge: string }): string
|
||
exchangeCodeForToken(config: OidcConfig, code: string, verifier: string): Promise<TokenSet>
|
||
getUserinfo(url: string, accessToken: string): Promise<OidcUserinfo>
|
||
```
|
||
|
||
完整的 OIDC 授权码 + PKCE 流程,基于 Authelia 实现。
|
||
|
||
#### middleware.ts
|
||
|
||
```typescript
|
||
createMiddleware(config: AuthConfig): NextMiddleware
|
||
```
|
||
|
||
Next.js 路由守卫工厂。验证 `tlyq_session` cookie(JWT payload 解码,检查过期时间)。公开路径放行,管理路径检查 admin 角色,未认证请求重定向到 `/login`。
|
||
|
||
#### user-sync.ts
|
||
|
||
```typescript
|
||
syncOidcUser(store: UserSyncStore, userinfo: OidcUserinfo): Promise<{ id: number, role: string, isNew: boolean }>
|
||
```
|
||
|
||
将 OIDC userinfo 同步到本地 SQLite users 表。`UserSyncStore` 是依赖注入接口,不耦合具体数据库驱动。
|
||
|
||
---
|
||
|
||
### lib/alert/ — 告警引擎
|
||
|
||
告警决策和健康检查的核心引擎。
|
||
|
||
#### types.ts
|
||
|
||
```typescript
|
||
type AlertLevel = 'critical' | 'warning' | 'info'
|
||
type ServiceStatus = 'normal' | 'abnormal' | 'unknown'
|
||
type CheckType = 'http' | 'docker' | 'ssh'
|
||
type AlertType = 'alert' | 'recovery' | 'flapping'
|
||
|
||
interface HealthCheckConfig {
|
||
type: CheckType
|
||
url?: string // HTTP 检查
|
||
expectedStatus?: number
|
||
expectedBody?: string
|
||
containerName?: string // Docker 检查
|
||
command?: string // SSH 检查
|
||
}
|
||
|
||
interface CheckResult {
|
||
success: boolean
|
||
responseTime?: number
|
||
errorMessage?: string
|
||
}
|
||
|
||
interface AlertDecision {
|
||
shouldAlert: boolean
|
||
reason?: string
|
||
suppressedBy?: 'quiet_period' | 'cooldown' | 'flapping'
|
||
}
|
||
|
||
interface AlertChannelConfig {
|
||
id: number
|
||
levelCritical: boolean
|
||
levelWarning: boolean
|
||
levelInfo: boolean
|
||
quietEnabled: boolean
|
||
quietStart?: string
|
||
quietEnd?: string
|
||
quietBypassCritical: boolean
|
||
cooldownMinutes: number
|
||
}
|
||
```
|
||
|
||
#### alert-manager.ts
|
||
|
||
```typescript
|
||
class AlertManager {
|
||
constructor(cooldownProvider: CooldownProvider)
|
||
evaluate(channel: AlertChannelConfig, serviceId: number, level: AlertLevel, now?: Date): AlertDecision
|
||
isQuietPeriod(channel: AlertChannelConfig, now?: Date): boolean
|
||
isCoolingDown(channelId: number, serviceId: number, level: AlertLevel, cooldownMinutes: number): boolean
|
||
recordAlert(channelId: number, serviceId: number, level: AlertLevel): void
|
||
}
|
||
```
|
||
|
||
三阶段告警决策:级别匹配 → 免打扰检查(支持跨午夜) → 冷却窗口检查。
|
||
|
||
`CooldownProvider` 是依赖注入接口:
|
||
|
||
```typescript
|
||
interface CooldownProvider {
|
||
getCooldown(key: string): number | null // 返回 Unix 时间戳
|
||
setCooldown(key: string, timestamp: number): void
|
||
}
|
||
```
|
||
|
||
#### health-checker.ts
|
||
|
||
```typescript
|
||
class HealthChecker {
|
||
registerChecker(type: CheckType, handler: Checker): void
|
||
check(checks: HealthCheckConfig[], timeoutMs: number): Promise<CheckResult[]>
|
||
}
|
||
|
||
class HttpChecker implements Checker { ... }
|
||
class DockerChecker implements Checker { ... }
|
||
```
|
||
|
||
策略模式:通过 `registerChecker()` 注册检查器,新检查类型(如 SSH)可无缝扩展。内部使用 `Promise.allSettled` 并发执行。
|
||
|
||
---
|
||
|
||
### lib/audit/ — 审计日志
|
||
|
||
#### audit-schema.ts
|
||
|
||
```typescript
|
||
const AUDIT_LOGS_TABLE_SQL: string // audit_logs 表 DDL
|
||
```
|
||
|
||
#### write-audit-log.ts
|
||
|
||
```typescript
|
||
interface AuditLogEntry {
|
||
userId?: number
|
||
username?: string
|
||
action: string
|
||
entityType: string
|
||
entityId?: number
|
||
details?: Record<string, unknown>
|
||
ipAddress?: string
|
||
}
|
||
|
||
interface AuditStore {
|
||
exec(sql: string): void
|
||
}
|
||
|
||
function writeAuditLog(store: AuditStore, entry: AuditLogEntry): void
|
||
```
|
||
|
||
使用 SQL 字符串拼接 + 单引号转义(非参数化查询),这是已知的设计选择。
|
||
|
||
---
|
||
|
||
### lib/db/ — 数据库 Schema
|
||
|
||
#### alert-schema.ts
|
||
|
||
```typescript
|
||
const SERVICES_TABLE_SQL: string
|
||
const ALERT_CHANNELS_TABLE_SQL: string
|
||
const ALERT_SETTINGS_TABLE_SQL: string
|
||
const STATUS_HISTORY_TABLE_SQL: string
|
||
const ALERT_HISTORY_TABLE_SQL: string
|
||
const ALL_TABLES_SQL: string // 以上 5 个的拼接
|
||
```
|
||
|
||
定义监控相关的 5 张核心表,供各站点初始化数据库使用。
|
||
|
||
---
|
||
|
||
### lib/wechat/ — 企业微信 Webhook
|
||
|
||
#### message-formatter.ts
|
||
|
||
```typescript
|
||
formatAvailabilityMessage(params: { serviceName: string, status: string, ... }): string
|
||
formatAlertLevel(level: AlertLevel): string // 返回带 emoji 的级别标签
|
||
```
|
||
|
||
#### wechat-pusher.ts
|
||
|
||
```typescript
|
||
class WeChatPusher {
|
||
constructor(webhookUrl: string)
|
||
pushMarkdown(title: string, content: string): Promise<boolean>
|
||
pushText(content: string): Promise<boolean>
|
||
}
|
||
|
||
class WeChatPusherCompat {
|
||
// 兼容 issue-ai 的旧接口(返回 boolean)
|
||
pushMarkdown(title: string, content: string): Promise<boolean>
|
||
}
|
||
```
|
||
|
||
5 秒超时,markdown 格式推送。`WeChatPusherCompat` 为迁移提供向后兼容。
|
||
|
||
---
|
||
|
||
### ui/ — React 组件库
|
||
|
||
所有组件均为 `'use client'`(Next.js App Router 兼容),使用 Tailwind CSS 工具类,支持 `dark:` 暗色模式。
|
||
|
||
**导出方式**:
|
||
|
||
```typescript
|
||
import { Button, Card, Badge, Table, Modal, Input, Select, Toast, Pagination } from '@shared/ui'
|
||
import LoginPage from '@shared/ui/login-page' // 单独导入,不在 barrel 中
|
||
```
|
||
|
||
| 组件 | 核心 Props | 说明 |
|
||
|------|-----------|------|
|
||
| Button | `variant: 'primary'|'secondary'|'danger'|'ghost'`, `size: 'sm'|'md'|'lg'`, `loading: boolean` | forwardRef,loading 时显示 SVG spinner |
|
||
| Badge | `variant: 'default'|'success'|'warning'|'danger'|'info'` | 药丸形状标签 |
|
||
| Card | `children`, `className?` | 白色/暗色圆角容器 |
|
||
| Input | `label?`, `error?` | 带标签和错误提示的输入框 |
|
||
| Select | `label?`, `options: {value, label}[]` | 下拉选择器 |
|
||
| Modal | `open: boolean`, `onClose`, `title?`, `maxWidth?` | 无 portal 模态框,backdrop 点击关闭,锁定 body scroll |
|
||
| Table | `headers: string[]`, `children: ReactNode` | 样式化 thead,tbody 由调用方提供 |
|
||
| Toast | `message`, `type: 'success'|'error'|'info'`, `onClose` | 右下角固定通知条 |
|
||
| Pagination | `page`, `totalPages`, `onPageChange` | 中文标签,totalPages <= 1 时隐藏 |
|
||
| useDirtyTracker | `isDirty(key, item): boolean`, `markClean(key, item): void`, `removeItem(key): void` | 脏状态追踪 Hook,"修改后才显示保存按钮"模式 |
|
||
| LoginPage | `siteName: string`, `title?: string` | 完整登录页,支持 SSO 和 LDAP 切换 |
|
||
|
||
---
|
||
|
||
## 如何导入
|
||
|
||
### tsconfig.json 配置
|
||
|
||
在消费方项目的 `tsconfig.json` 中添加路径映射:
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"paths": {
|
||
"@/*": ["./src/*"],
|
||
"@shared/*": ["./shared/*"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 符号链接
|
||
|
||
在消费方项目根目录创建符号链接:
|
||
|
||
```bash
|
||
cd /Users/niuniu/programs/docker/monitor-ai
|
||
ln -s ../shared shared
|
||
```
|
||
|
||
### 导入示例
|
||
|
||
```typescript
|
||
// 后端认证
|
||
import { signJwt, verifyJwt } from '@shared/lib/auth/jwt'
|
||
import { ldapAuth } from '@shared/lib/auth/ldap'
|
||
import { discoverOidcConfig, buildAuthorizeUrl } from '@shared/lib/auth/oidc'
|
||
|
||
// 告警引擎
|
||
import { AlertManager } from '@shared/lib/alert/alert-manager'
|
||
import { HealthChecker, HttpChecker, DockerChecker } from '@shared/lib/alert/health-checker'
|
||
|
||
// 审计日志
|
||
import { writeAuditLog } from '@shared/lib/audit/write-audit-log'
|
||
|
||
// 数据库 Schema
|
||
import { ALL_TABLES_SQL } from '@shared/lib/db/alert-schema'
|
||
|
||
// 企业微信
|
||
import { WeChatPusher } from '@shared/lib/wechat/wechat-pusher'
|
||
|
||
// UI 组件
|
||
import { Button, Card, Badge, Table, Modal, Input, Select, Toast, Pagination } from '@shared/ui'
|
||
import LoginPage from '@shared/ui/login-page'
|
||
```
|
||
|
||
---
|
||
|
||
## 设计模式
|
||
|
||
| 模式 | 应用位置 | 说明 |
|
||
|------|---------|------|
|
||
| 依赖注入 | `CooldownProvider`, `UserSyncStore`, `AuditStore`, `Checker` 接口 | 通过接口注入外部状态,不耦合具体数据库驱动 |
|
||
| 策略模式 | `HealthChecker.registerChecker()` | 可插拔检查器,新检查类型无需修改引擎 |
|
||
| 工厂模式 | `createMiddleware(config)` | 返回配置化的 Next.js middleware |
|
||
| 适配器模式 | `WeChatPusherCompat` | 包装 `WeChatPusher` 提供旧接口兼容 |
|
||
| Barrel 导出 | `ui/index.tsx` | 统一导出入口,消费方一行导入 |
|