- 项目重命名为怒月(Nuyue) - 服务端基础骨架 (Gin + GORM) - 数据库支持 PostgreSQL/SQLite 双模式 - Redis 可选配置 - 加密工具包 (Argon2id + HKDF + AES-256-GCM) - 统一响应封装 - 配置加载 (Viper) - 安装向导 API 设计文档 - Termius 深色主题设计规范 - 开发设计文档完善
99 KiB
怒月 (Nuyue) - 服务器监控探针系统
Git 仓库:git.viaeon.com/admin/nuyue
开发流程
每个模块开发完成后:
- Code Review - 检查代码质量、安全性、性能
- 测试验证 - 单元测试、集成测试
- 推送到 Git -
git push origin main - 更新文档 - 同步更新 DESIGN.md
1. 项目概述
怒月 (Nuyue) 是一个服务器监控探针系统,由 服务端(Server) 和 客户端(Agent) 两部分组成。服务端集成于官网,提供用户注册、套餐购买、服务器管理、监控展示等功能;客户端部署在用户服务器上,通过 gRPC 上报系统指标。
核心设计原则
- 零知识安全:服务端不存储用户明文密码和敏感配置,所有敏感信息由用户密码派生的密钥加密后存储
- 配置下发:客户端所有运行配置(上报频率、TCPing 节点、备份策略等)均从服务端获取并缓存
- 多租户隔离:用户数据严格隔离,每个用户拥有独立的探针展示页面
2. 技术选型
| 组件 | 技术方案 | 说明 |
|---|---|---|
| 服务端框架 | Go (Gin) | 高性能、gRPC 原生支持 |
| 客户端 | Go | 单二进制、跨平台、低资源占用 |
| 通信协议 | gRPC + Protobuf | 双向流、高效序列化 |
| 数据库 | PostgreSQL (生产) / SQLite (单机) | |
| 缓存 | Redis (生产可选) | |
| 前端 | Vue 3 + TypeScript | 管理后台 + 用户面板 |
| UI 组件库 | Naive UI | 遵循 Termius 设计规范:深色终端风格 + 绿色品牌色 + JetBrains Mono 字体 |
| 探针页面 | Nuxt 3 (SSR) | 用户公开监控页面,支持自定义 |
| 消息通知 | Telegram Bot API | 告警通知 |
| 定时任务 | Go 内置 ticker + cron | 客户端本地调度 |
| 对象存储 | S3 兼容 | 备份文件存储 |
3. 系统架构
┌─────────────────────────────────────────────────────────┐
│ 服务端 (Server) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌─────────┐ │
│ │ 官网前端 │ │ 管理后台 │ │ 用户面板 │ │ 探针页面 │ │
│ │ (Vue3) │ │ (Vue3) │ │ (Vue3) │ │ (Nuxt3) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬────┘ │
│ └──────────────┴─────────────┴─────────────┘ │
│ │ REST API │
│ ┌──────────────────────┴──────────────────────────┐ │
│ │ API Gateway (Gin) │ │
│ │ ┌─────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ Auth │ │ Plan │ │ Server Mgmt │ │ │
│ │ │ Module │ │ Module │ │ Module │ │ │
│ │ └─────────┘ └──────────┘ └────────────────┘ │ │
│ │ ┌─────────┐ ┌──────────┐ ┌────────────────┐ │ │
│ │ │ Redeem │ │ Monitor │ │ Backup │ │ │
│ │ │ Module │ │ Module │ │ Module │ │ │
│ │ └─────────┘ └──────────┘ └────────────────┘ │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────────┴──────────────────────────┐ │
│ │ gRPC Service │ │
│ │ ┌──────────┐ ┌───────────┐ ┌───────────────┐ │ │
│ │ │ Report │ │ Config │ │ Backup │ │ │
│ │ │ Stream │ │ Fetch │ │ Upload │ │ │
│ │ └──────────┘ └───────────┘ └───────────────┘ │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────┐ ┌───────┴──────┐ ┌────────────────┐ │
│ │ PostgreSQL│ │ Redis │ │ S3 Storage │ │
│ └──────────┘ └──────────────┘ └────────────────┘ │
└─────────────────────────┬───────────────────────────────┘
│ gRPC (TLS)
┌───────────────┼───────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Agent 1 │ │ Agent 2 │ │ Agent N │
│ (Go) │ │ (Go) │ │ (Go) │
└───────────┘ └───────────┘ └───────────┘
4. 数据库设计
4.0 数据库选型
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 生产环境 | PostgreSQL | 高并发、多租户、支持 TimescaleDB 扩展用于指标数据 |
| 小型部署 | SQLite | 单机、低资源、嵌入式部署,零依赖,适合个人使用 |
| 开发环境 | SQLite | 快速启动、零配置,无需安装数据库服务 |
SQLite 使用限制:
- 不支持 TimescaleDB,指标数据直接存储在
server_metrics表(按天分区或定期清理) - 单写入连接,适合单实例部署,不支持多实例负载均衡
- 备份功能建议使用本地文件备份而非 S3
- 无 Redis 时,配置缓存使用内存缓存(进程重启后重新加载)
数据库兼容性设计:
- 使用 GORM 作为 ORM,自动适配 PostgreSQL 和 SQLite
- 所有表结构使用通用类型,避免 PostgreSQL 特有类型(如
BYTEA在 SQLite 中用BLOB) - 指标数据表在 SQLite 模式下使用普通表 + 定期清理策略
4.1 用户表 (users)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| username | VARCHAR(50) | 账号,唯一,注册后不可改 |
| password_hash | VARCHAR(255) | Argon2id 哈希 |
| VARCHAR(255) | 绑定邮箱,可换绑,唯一,可为 NULL | |
| email_verified | BOOLEAN | 邮箱是否已验证 |
| avatar_url | VARCHAR(500) | 头像 |
| role | VARCHAR(20) | admin / user |
| status | VARCHAR(20) | active / disabled |
| encrypted_config_key | BYTEA | 加密密码加密的配置密钥(用于加解密客户端配置) |
| config_key_nonce | BYTEA | 加密 nonce |
| tg_chat_id | BIGINT | TG 绑定 Chat ID,NULL 表示未绑定 |
| tg_username | VARCHAR(100) | TG 用户名 |
| tg_bound_at | TIMESTAMP | TG 绑定时间 |
| preference_show_remaining_value | BOOLEAN | 是否显示套餐剩余价值,默认 true |
| preference_tg_notify_enabled | BOOLEAN | 是否开启 TG 告警通知,默认 false |
| created_at | TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMP | 更新时间 |
4.2 邮箱验证码表 (email_verification_codes)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| VARCHAR(255) | 邮箱 | |
| code | VARCHAR(6) | 验证码 |
| purpose | VARCHAR(20) | register / bind / rebind / reset_password |
| expires_at | TIMESTAMP | 过期时间 |
| used | BOOLEAN | 是否已使用 |
| ip_address | VARCHAR(45) | 请求 IP |
| created_at | TIMESTAMP |
4.3 系统设置表 (system_settings)
键值对存储,管理员在后台配置,服务启动时加载到 Redis 缓存
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| category | VARCHAR(30) | 分类:general / smtp / payment / security / telegram |
| key | VARCHAR(100) | 配置键,唯一索引 |
| value | TEXT | 配置值(敏感值加密存储) |
| value_type | VARCHAR(20) | string / int / bool / json |
| description | VARCHAR(255) | 配置说明 |
| is_encrypted | BOOLEAN | 值是否加密存储 |
| updated_by | UUID | FK → users,最后修改者 |
| updated_at | TIMESTAMP |
系统配置项清单:
| 分类 | key | 说明 | 默认值 |
|---|---|---|---|
| general | site_name | 站点名称 | StatusProbe |
| general | site_url | 站点地址 | https://status.example.com |
| general | site_description | 站点描述 | |
| smtp | smtp_host | SMTP 服务器 | |
| smtp | smtp_port | SMTP 端口 | 465 |
| smtp | smtp_username | SMTP 用户名 | |
| smtp | smtp_password | SMTP 密码(加密存储) | |
| smtp | smtp_from_name | 发件人名称 | StatusProbe |
| smtp | smtp_from_email | 发件人邮箱 | |
| smtp | smtp_encryption | 加密方式 ssl/tls/starttls | ssl |
| payment | payment_alipay_enabled | 支付宝是否启用 | false |
| payment | payment_alipay_app_id | 支付宝 App ID | |
| payment | payment_alipay_private_key | 支付宝私钥(加密存储) | |
| payment | payment_alipay_public_key | 支付宝公钥 | |
| payment | payment_wechat_enabled | 微信支付是否启用 | false |
| payment | payment_wechat_mch_id | 微信商户号 | |
| payment | payment_wechat_api_key | 微信 API 密钥(加密存储) | |
| payment | payment_wechat_cert_sn | 微信证书序列号 | |
| payment | payment_stripe_enabled | Stripe 是否启用 | false |
| payment | payment_stripe_publishable_key | Stripe 公钥 | |
| payment | payment_stripe_secret_key | Stripe 密钥(加密存储) | |
| payment | payment_stripe_webhook_secret | Stripe Webhook 签名密钥(加密存储) | |
| security | register_enabled | 是否开放注册 | true |
| security | captcha_enabled | 注册是否开启验证码 | false |
| security | captcha_type | 验证码类型 recaptcha/hcaptcha/turnstile | turnstile |
| security | captcha_site_key | 验证码 Site Key | |
| security | captcha_secret_key | 验证码 Secret Key(加密存储) | |
| security | email_verify_enabled | 注册是否需要邮箱验证 | false |
| security | email_verify_bind | 是否强制绑定邮箱 | false |
| security | login_captcha_enabled | 登录是否开启验证码 | false |
| security | rate_limit_register | 注册频率限制(IP/小时) | 5 |
| security | rate_limit_login | 登录频率限制(IP/小时) | 20 |
| telegram | tg_bot_enabled | 是否启用系统 TG Bot | false |
| telegram | tg_bot_token | 系统 TG Bot Token(加密存储) | |
| telegram | tg_bot_username | 系统 TG Bot 用户名 | |
| telegram | tg_notify_enabled | 系统全局 TG 通知开关 | true |
| telegram | tg_bind_welcome_msg | 用户绑定 Bot 时的欢迎消息 | 欢迎绑定 StatusProbe 通知! |
4.4 支付订单表 (payment_orders)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| user_id | UUID | FK → users |
| user_plan_id | UUID | FK → user_plans,支付成功后关联 |
| order_no | VARCHAR(32) | 订单号,唯一索引 |
| channel | VARCHAR(20) | alipay / wechat / stripe |
| amount | DECIMAL(10,2) | 金额 |
| currency | VARCHAR(10) | 货币 CNY/USD |
| plan_id | UUID | FK → plans |
| billing_type | VARCHAR(20) | monthly / yearly / lifetime |
| type | VARCHAR(20) | purchase / upgrade |
| status | VARCHAR(20) | pending / paid / failed / refunded / expired |
| channel_trade_no | VARCHAR(100) | 第三方交易号 |
| paid_at | TIMESTAMP | 支付时间 |
| expires_at | TIMESTAMP | 订单过期时间(未支付自动关闭) |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP |
4.5 套餐表 (plans)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| name | VARCHAR(100) | 套餐名称 |
| description | TEXT | 套餐描述 |
| max_clients | INT | 允许的客户端数量 |
| max_tcping_nodes | INT | 最大 TCPing 节点数 |
| allow_custom_theme | BOOLEAN | 是否允许自定义主题和组件 |
| allow_tg_notify | BOOLEAN | 是否允许 TG 通知 |
| allow_backup | BOOLEAN | 是否允许定时备份 |
| max_backup_count | INT | 最大备份仓库数 |
| max_backup_repos | INT | 最大 Restic 仓库数 |
| allow_backup_docker | BOOLEAN | 是否允许 Docker 备份 |
| allow_backup_database | BOOLEAN | 是否允许数据库备份 |
| allow_upgrade | BOOLEAN | 是否允许升级 |
| price_monthly | DECIMAL(10,2) | 月付价格,NULL 表示不可月付 |
| price_yearly | DECIMAL(10,2) | 年付价格,NULL 表示不可年付 |
| price_lifetime | DECIMAL(10,2) | 永久价格,NULL 表示不可永久 |
| sort_order | INT | 排序 |
| is_visible | BOOLEAN | 是否可见 |
| status | VARCHAR(20) | active / archived |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP |
4.6 用户套餐表 (user_plans)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| user_id | UUID | FK → users |
| plan_id | UUID | FK → plans |
| billing_type | VARCHAR(20) | monthly / yearly / lifetime |
| started_at | TIMESTAMP | 生效时间 |
| expires_at | TIMESTAMP | 过期时间,NULL 表示永久 |
| upgraded_from | UUID | FK → user_plans,升级来源 |
| status | VARCHAR(20) | active / expired / cancelled |
| created_at | TIMESTAMP |
4.7 兑换码表 (redeem_codes)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| code | VARCHAR(32) | 兑换码,唯一索引 |
| plan_id | UUID | FK → plans |
| billing_type | VARCHAR(20) | monthly / yearly / lifetime |
| max_use_count | INT | 最大使用次数 |
| used_count | INT | 已使用次数,默认 0 |
| created_by | UUID | FK → users,创建者 |
| expires_at | TIMESTAMP | 兑换码过期时间 |
| status | VARCHAR(20) | active / used_up / expired |
| created_at | TIMESTAMP |
4.8 兑换记录表 (redeem_records)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| redeem_code_id | UUID | FK → redeem_codes |
| user_id | UUID | FK → users |
| user_plan_id | UUID | FK → user_plans |
| created_at | TIMESTAMP |
4.9 服务器表 (servers)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| user_id | UUID | FK → users |
| name | VARCHAR(100) | 服务器名称 |
| display_name | VARCHAR(100) | 展示名称 |
| region | VARCHAR(50) | 地区标签 |
| tags | VARCHAR[] | 标签数组 |
| agent_token | VARCHAR(64) | 客户端认证令牌(随机生成,非用户密码) |
| encrypted_config | BYTEA | AES-256-GCM 加密的客户端配置 |
| config_nonce | BYTEA | 加密 nonce |
| last_seen_at | TIMESTAMP | 最后上报时间 |
| status | VARCHAR(20) | online / offline / unconnected |
| is_visible | BOOLEAN | 是否在探针页面展示 |
| sort_order | INT | 排序 |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP |
4.10 服务器指标表 (server_metrics)
使用 TimescaleDB 扩展或按时间分区
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGSERIAL | 主键 |
| server_id | UUID | FK → servers |
| cpu_usage | REAL | CPU 使用率 % |
| cpu_temp | REAL | CPU 温度 ℃,NULL 表示不支持/无法读取 |
| gpu_info | JSONB | GPU 信息数组,如 [{"name":"RTX 4090","temp":65.0,"usage":45.0,"memory_total":24576,"memory_used":8192}],NULL 表示无 GPU |
| memory_total | BIGINT | 总内存 bytes |
| memory_used | BIGINT | 已用内存 bytes |
| disk_total | BIGINT | 总磁盘 bytes |
| disk_used | BIGINT | 已用磁盘 bytes |
| network_rx | BIGINT | 入站流量 bytes/s |
| network_tx | BIGINT | 出站流量 bytes/s |
| load_1 | REAL | 1分钟负载 |
| load_5 | REAL | 5分钟负载 |
| load_15 | REAL | 15分钟负载 |
| uptime | BIGINT | 运行时间 seconds |
| os_info | JSONB | 操作系统信息 |
| process_count | INT | 进程数 |
| reported_at | TIMESTAMP | 上报时间 |
4.11 TCPing 记录表 (tcping_results)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGSERIAL | 主键 |
| server_id | UUID | FK → servers |
| target_host | VARCHAR(255) | 目标地址 |
| target_port | INT | 目标端口 |
| latency_ms | REAL | 延迟 ms,-1 表示超时 |
| success | BOOLEAN | 是否连通 |
| checked_at | TIMESTAMP | 检测时间 |
4.12 TCPing 节点配置表 (tcping_nodes)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| name | VARCHAR(100) | 节点名称 |
| host | VARCHAR(255) | 地址 |
| port | INT | 端口 |
| is_default | BOOLEAN | 是否默认节点 |
| status | VARCHAR(20) | active / disabled |
| created_at | TIMESTAMP |
4.13 告警规则表 (alert_rules)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| server_id | UUID | FK → servers |
| metric_type | VARCHAR(30) | cpu / memory / disk / cpu_temp / gpu_temp / gpu_usage / tcping_offline |
| operator | VARCHAR(10) | gt / gte / lt / lte / eq |
| threshold | REAL | 阈值 |
| duration_seconds | INT | 持续时间,0 表示即时 |
| cooldown_seconds | INT | 冷却时间,默认 300 |
| is_enabled | BOOLEAN | 是否启用 |
| created_at | TIMESTAMP |
4.14 通知渠道表 (notify_channels)
用户无需自行配置 Bot Token,管理员统一配置系统 TG Bot,用户只需绑定自己的 TG 账号
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| user_id | UUID | FK → users |
| type | VARCHAR(20) | telegram |
| name | VARCHAR(100) | 渠道名称 |
| is_enabled | BOOLEAN | 是否启用 |
| created_at | TIMESTAMP |
用户的 TG Chat ID 和 TG 用户名存储在 users 表(tg_chat_id, tg_username),不在 notify_channels 重复存储。 notify_channels 预留 type 字段为将来扩展其他通知渠道(如 Email、Webhook 等)。
4.15 告警记录表 (alert_events)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| alert_rule_id | UUID | FK → alert_rules |
| server_id | UUID | FK → servers |
| metric_value | REAL | 触发时的值 |
| status | VARCHAR(20) | firing / resolved |
| notified | BOOLEAN | 是否已通知 |
| fired_at | TIMESTAMP | |
| resolved_at | TIMESTAMP |
4.16 备份记录表 (backup_records)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| server_id | UUID | FK → servers |
| file_key | VARCHAR(255) | S3 对象 key |
| file_size | BIGINT | 文件大小 |
| encrypted | BOOLEAN | 是否加密 |
| created_at | TIMESTAMP |
4.17 探针页面配置表 (probe_pages)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| user_id | UUID | FK → users,唯一 |
| slug | VARCHAR(50) | 页面路径标识,唯一 |
| title | VARCHAR(100) | 页面标题 |
| description | TEXT | 页面描述 |
| logo_url | VARCHAR(500) | Logo URL |
| favicon_url | VARCHAR(500) | Favicon URL |
| footer_text | VARCHAR(200) | 页脚自定义文字 |
| footer_link_url | VARCHAR(500) | 页脚链接 |
| footer_link_text | VARCHAR(50) | 页脚链接文字 |
| theme_id | VARCHAR(30) | 主题 ID,见 13.7 探针页面主题 |
| primary_color | VARCHAR(7) | 主色调覆盖,如 #3B82F6,NULL 使用主题默认 |
| layout_columns | INT | 卡片网格列数,0 表示自适应 |
| visible_components | JSONB | 显示组件配置,见下方说明 |
| visible_server_ids | UUID[] | 可见的服务器 ID 列表 |
| is_published | BOOLEAN | 是否发布 |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP |
visible_components 配置示例:
{
"cpu": { "show": true, "show_temp": true },
"gpu": { "show": true, "show_temp": true, "show_memory": true },
"memory": { "show": true, "show_percentage": true },
"disk": { "show": true, "show_percentage": false },
"network": { "show": true, "show_speed": true },
"uptime": { "show": true, "format": "auto" },
"tcping": { "show": true, "show_latency": true },
"os_info": { "show": false },
"tags": { "show": true },
"network_speed": { "show": true }
}
5. 安全设计
5.1 零知识加密架构
核心原则:服务端永远不接触用户的明文密码和敏感配置。
系统中有两种密码,用途完全独立:
| 密码类型 | 用途 | 存储方式 |
|---|---|---|
| 账号密码 | 登录认证 | Argon2id hash 存 DB |
| 加密密码 | 加解密客户端配置、备份凭证等 | 不存储,仅用户记忆 |
加密密码由用户独立设定,不一定是账号密码。用户可以使用相同或不同的密码。
┌─────────────────────────────────────────────────────────────┐
│ 账号密码体系 │
│ │
│ 账号密码 ──→ Argon2id(密码, random_salt) ──→ password_hash │
│ [存储到 DB] │
│ [用于登录验证] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 加密密码体系 │
│ │
│ 加密密码 ──→ HKDF-SHA256(密码, user_id+"enc") ──→ enc_key │
│ [内存中] │
│ │ │
│ ┌────────────┴────────────┐ │
│ │ │ │
│ 加密 enc_key 加密配置数据 │
│ AES-256-GCM(enc_key, AES-256-GCM(enc_key, │
│ random_config_key) config_json) │
│ │ │ │
│ encrypted_config_key encrypted_config │
│ [存储到 DB] [存储到 DB] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ config_key 用途(解密后仅在内存) │
│ │
│ config_key ──→ 解密 servers.encrypted_config │
│ config_key ──→ 解密 notify_channels.encrypted_config │
│ config_key ──→ 解密备份配置中的凭证 │
│ config_key ──→ 加密 Agent 安装配置 │
└─────────────────────────────────────────────────────────────┘
5.2 密码与密钥流程
-
注册:
- 前端:
password_hash = Argon2id(账号密码, random_salt)→ 发送到服务端 - 前端:
enc_key = HKDF-SHA256(加密密码, user_id + "enc")→ 仅存内存 - 前端:生成随机
config_key(32 字节),encrypted_config_key = AES-256-GCM(enc_key, config_key) - 发送
{ username, password_hash, encrypted_config_key, config_key_nonce }到服务端存储 - 如果加密密码与账号密码相同,用户只需输入一次;不同则需分别输入
- 前端:
-
登录:
- 前端:
password_hash = Argon2id(账号密码, random_salt)→ 发送验证 - 验证通过后返回
encrypted_config_key - 前端提示用户输入加密密码 →
enc_key = HKDF-SHA256(加密密码, user_id + "enc") - 前端:
config_key = AES-256-GCM-Decrypt(enc_key, encrypted_config_key) config_key存于前端内存(sessionStorage),后续所有配置加解密使用config_key
- 前端:
-
配置加密:
- 用户输入敏感配置(TG Token、备份凭证、S3 AK/SK 等)
- 前端:
encrypted_config = AES-256-GCM(config_key, config_json) - 发送
{ encrypted_config, nonce }到服务端,服务端只存密文
-
一键安装命令生成:
- 用户在前端输入加密密码 → 派生
enc_key→ 解密config_key - 前端组装 Agent 配置(含 agent_token 等)
- 前端:
encrypted_agent_config = AES-256-GCM(config_key, agent_config_json) - 生成安装命令:
curl ... | bash -s -- --token=xxx --encrypted-config=base64(...) - Agent 安装后首次连接时,需输入加密密码解密配置
- 服务端不存储、不接触加密密码和 config_key
- 用户在前端输入加密密码 → 派生
-
修改加密密码:
- 前端:输入旧加密密码 → 解密得到
config_key - 前端:输入新加密密码 →
new_enc_key = HKDF-SHA256(新密码, user_id + "enc") - 前端:
new_encrypted_config_key = AES-256-GCM(new_enc_key, config_key) - 发送
{ new_encrypted_config_key, new_config_key_nonce }到服务端更新 - config_key 不变,所有已加密配置无需重新加密
- 前端:输入旧加密密码 → 解密得到
5.3 客户端认证
- 每台服务器生成唯一的
agent_token(64 字节随机),非用户密码 - gRPC 连接使用 TLS +
agent_token认证 agent_token通过加密配置下发,服务端明文存储(它本身不是用户密码)
5.4 安全要点
| 项目 | 方案 |
|---|---|
| 账号密码 | Argon2id 哈希,前端计算,服务端只存 hash |
| 加密密码 | 不存储,仅用户记忆,用于派生 enc_key 解密 config_key |
| 敏感配置 | AES-256-GCM 加密,密钥为 config_key(由加密密码派生的 enc_key 保护) |
| 通信加密 | gRPC 强制 TLS 1.3 |
| API 认证 | JWT (RS256),Access Token 15min + Refresh Token 7d |
| 限流 | Redis 令牌桶,API 按用户限流 |
| SQL 注入 | ORM 参数化查询 |
| XSS | CSP Header + 输入过滤 |
6. gRPC 协议设计
6.1 Proto 定义
syntax = "proto3";
package statusprobe;
// ============ 客户端上报服务 ============
service AgentService {
// 双向流:客户端上报指标,服务端可下发指令
rpc ReportStream(stream ReportRequest) returns (stream ServerCommand);
// 客户端拉取配置
rpc FetchConfig(ConfigRequest) returns (AgentConfig);
// 备份文件上传
rpc UploadBackup(stream BackupChunk) returns (UploadBackupResponse);
// 心跳(轻量级,用于保活检测)
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse);
}
// ============ 上报请求 ============
message ReportRequest {
string agent_token = 1;
oneof payload {
SystemMetrics metrics = 2;
TcpingResults tcping = 3;
BackupResult backup_result = 4;
}
}
message SystemMetrics {
double cpu_usage = 1;
int64 memory_total = 2;
int64 memory_used = 3;
int64 disk_total = 4;
int64 disk_used = 5;
int64 network_rx = 6;
int64 network_tx = 7;
double load_1 = 8;
double load_5 = 9;
double load_15 = 10;
int64 uptime = 11;
string os_info = 12; // JSON: {os, arch, kernel}
int32 process_count = 13;
double cpu_temp = 14; // CPU 温度 ℃,-1 表示不支持
repeated GpuInfo gpus = 15; // GPU 信息列表
}
message GpuInfo {
string name = 1; // GPU 名称,如 "NVIDIA RTX 4090"
double temp = 2; // GPU 温度 ℃,-1 表示不支持
double usage = 3; // GPU 使用率 %
int64 memory_total = 4; // 显存总量 MB
int64 memory_used = 5; // 显存已用 MB
}
message TcpingResult {
string target_host = 1;
int32 target_port = 2;
double latency_ms = 3; // -1 = timeout
bool success = 4;
}
message TcpingResults {
repeated TcpingResult results = 1;
}
// ============ 服务端下发指令 ============
message ServerCommand {
oneof command {
ConfigUpdateCommand config_update = 1; // 配置变更通知
RestartCommand restart = 2; // 重启客户端
BackupCommand backup = 3; // 触发备份
TcpingUpdateCommand tcping_update = 4; // TCPing 节点更新
}
}
message ConfigUpdateCommand {
string config_version = 1; // 配置版本号,客户端拉取新配置
}
message RestartCommand {}
message BackupCommand {
string backup_id = 1;
}
message TcpingUpdateCommand {
repeated TcpingTarget targets = 1;
}
message TcpingTarget {
string host = 1;
int32 port = 2;
}
// ============ 配置拉取 ============
message ConfigRequest {
string agent_token = 1;
string current_config_version = 2; // 客户端当前配置版本
}
message AgentConfig {
string config_version = 1;
int32 report_interval_seconds = 2; // 上报频率
int32 tcping_interval_seconds = 3; // TCPing 检测频率
repeated TcpingTarget tcping_targets = 4; // TCPing 目标节点
BackupConfig backup_config = 5; // 备份配置
bool config_changed = 6; // 配置是否有变更
}
message BackupConfig {
bool enabled = 1;
int32 interval_hours = 2; // 备份间隔
int32 max_count = 3; // 最大保留数
string encrypted_config_json = 4; // 加密的完整备份配置 JSON(含 repos、tasks 等)
}
// ============ 备份上传 ============
message BackupChunk {
string agent_token = 1;
string backup_id = 2;
string filename = 3;
bytes data = 4;
int64 total_size = 5;
bool is_last = 6;
}
message UploadBackupResponse {
bool success = 1;
string file_key = 2;
string message = 3;
}
// ============ 心跳 ============
message HeartbeatRequest {
string agent_token = 1;
}
message HeartbeatResponse {
bool ok = 1;
string config_version = 2; // 附带当前配置版本,客户端可判断是否需要拉取
}
6.2 通信流程
Agent 启动
│
├─→ FetchConfig(agent_token, "") ──→ 获取完整配置
│ └─→ 缓存到本地
│
├─→ ReportStream (双向流)
│ │
│ ├─→ 定时发送 SystemMetrics (按 report_interval)
│ ├─→ 定时发送 TcpingResults (按 tcping_interval)
│ │
│ └─← 接收 ServerCommand
│ ├─← ConfigUpdateCommand → 重新 FetchConfig
│ ├─← BackupCommand → 执行备份 → UploadBackup
│ ├─← TcpingUpdateCommand → 更新 TCPing 目标
│ └─← RestartCommand → 重启自身
│
└─→ Heartbeat (每 30s,当 ReportStream 断开时使用)
6.3 配置缓存机制
服务端:
用户修改配置 → 写入 DB → 更新 Redis 缓存 (key: config:{server_id}, version 递增)
Agent 请求配置 → 读 Redis 缓存 → 返回 (未命中则查 DB 并回填)
Redis 缓存结构:
config:{server_id} → AgentConfig protobuf 序列化
config_version:{server_id} → 版本号 (递增整数)
tcping_nodes:default → 默认 TCPing 节点列表
tcping_nodes:{user_id} → 用户自定义 TCPing 节点
7. REST API 设计
7.1 认证模块
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/auth/register | 注册 |
| POST | /api/v1/auth/login | 登录 |
| POST | /api/v1/auth/refresh | 刷新 Token |
| POST | /api/v1/auth/logout | 登出 |
| POST | /api/v1/auth/send-code | 发送邮箱验证码 |
| POST | /api/v1/auth/verify-email | 验证邮箱 |
| POST | /api/v1/auth/reset-password | 重置密码(需邮箱验证码) |
注册请求示例:
{
"username": "alice",
"password_hash": "argon2id$...", // 前端 Argon2id 计算
"email": "alice@example.com", // 可选,取决于系统配置
"email_code": "123456", // 邮箱验证码(email_verify_enabled 时必填)
"captcha_token": "...", // 验证码 token(captcha_enabled 时必填)
"encrypted_config_key": "base64...", // AES-256-GCM(enc_key, config_key)
"config_key_nonce": "base64..."
}
登录请求示例:
{
"username": "alice",
"password_hash": "argon2id$...",
"captcha_token": "..." // login_captcha_enabled 时必填
}
登录响应示例:
{
"access_token": "jwt...",
"refresh_token": "jwt...",
"user_id": "uuid",
"encrypted_config_key": "base64...",
"config_key_nonce": "base64..."
}
发送邮箱验证码:
// 请求
{
"email": "alice@example.com",
"purpose": "register" // register / bind / rebind / reset_password
}
// 响应
{
"message": "验证码已发送"
}
注册流程(根据系统配置动态变化):
captcha_enabled?
├─ Yes → 前端展示验证码 → 验证通过后允许提交
└─ No → 直接下一步
email_verify_enabled?
├─ Yes → 先 send-code → 用户输入验证码 → 一起提交注册
└─ No → 邮箱可选填
register_enabled?
├─ Yes → 正常注册
└─ No → 返回 "注册已关闭"
7.2 用户账号模块
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/user/profile | 获取个人信息 |
| PUT | /api/v1/user/profile | 更新个人信息(头像等) |
| PUT | /api/v1/user/password | 修改账号密码 |
| PUT | /api/v1/user/encryption-password | 修改加密密码(重新加密 config_key) |
| POST | /api/v1/user/bind-email | 绑定邮箱 |
| POST | /api/v1/user/rebind-email | 换绑邮箱(需旧邮箱验证码 + 新邮箱验证码) |
| POST | /api/v1/user/resend-verify | 重发邮箱验证 |
| GET | /api/v1/user/tg-bind-info | 获取 TG 绑定状态和绑定链接 |
| POST | /api/v1/user/unbind-tg | 解绑 TG |
| PUT | /api/v1/user/preferences | 更新用户偏好(剩余价值显示、TG 通知开关等) |
绑定邮箱:
{
"email": "new@example.com",
"code": "123456"
}
换绑邮箱:
{
"new_email": "new@example.com",
"new_email_code": "123456",
"old_email_code": "654321" // 旧邮箱验证码确认身份
}
修改账号密码:
{
"old_password_hash": "argon2id$...", // 旧密码哈希
"new_password_hash": "argon2id$..." // 新密码哈希
}
修改加密密码:
{
"new_encrypted_config_key": "base64...", // 用新加密密码派生的 enc_key 重新加密 config_key
"new_config_key_nonce": "base64..."
}
TG 绑定信息:
// 响应
{
"is_bound": true,
"tg_username": "@alice",
"bound_at": "2026-06-20T10:00:00Z",
"bind_url": "https://t.me/StatusProbeBot?start=bind_abc123" // 未绑定时返回,用户点击链接打开 TG
}
TG 绑定流程:
1. 用户点击 "绑定 TG" → 前端调用 GET /api/v1/user/tg-bind-info
2. 返回 bind_url → 用户点击链接,打开 TG 与 Bot 对话
3. Bot 收到 /start bind_abc123 → 调用服务端回调 API 确认绑定
4. 服务端更新 users.tg_chat_id 和 tg_username
5. 前端轮询或 WebSocket 通知绑定成功
更新用户偏好:
{
"show_remaining_value": false, // 关闭剩余价值显示
"tg_notify_enabled": true // 开启 TG 通知(需先绑定 TG)
}
7.3 套餐模块
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/plans | 套餐列表(公开) |
| GET | /api/v1/plans/:id | 套餐详情 |
| POST | /api/v1/admin/plans | 创建套餐 (Admin) |
| PUT | /api/v1/admin/plans/:id | 更新套餐 (Admin) |
| DELETE | /api/v1/admin/plans/:id | 归档套餐 (Admin) |
7.4 订阅与支付
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/subscriptions/purchase | 购买套餐 |
| POST | /api/v1/subscriptions/upgrade | 升级套餐 |
| GET | /api/v1/subscriptions/current | 当前订阅 |
| GET | /api/v1/subscriptions/history | 订阅历史 |
| POST | /api/v1/redeem | 兑换码兑换 |
升级逻辑:
- 计算剩余价值 = 原套餐价格 × (剩余天数 / 总天数)
- 差价 = 新套餐价格 - 剩余价值
- 差价 ≤ 0 时不收费,直接升级
- 永久套餐不可升级(已是最高)
7.5 兑换码模块 (Admin)
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/admin/redeem-codes/generate | 批量生成兑换码 |
| GET | /api/v1/admin/redeem-codes | 兑换码列表 |
| DELETE | /api/v1/admin/redeem-codes/:id | 禁用兑换码 |
生成请求:
{
"plan_id": "uuid",
"billing_type": "monthly",
"count": 10,
"max_use_count": 1,
"expires_at": "2026-12-31T23:59:59Z"
}
7.6 服务器管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/servers | 服务器列表 |
| POST | /api/v1/servers | 添加服务器 |
| PUT | /api/v1/servers/:id | 更新服务器信息 |
| DELETE | /api/v1/servers/:id | 删除服务器 |
| GET | /api/v1/servers/:id/metrics | 获取指标数据 |
| GET | /api/v1/servers/:id/tcping | 获取 TCPing 数据 |
| POST | /api/v1/servers/:id/install-command | 生成安装命令(需前端传入密码解密) |
| PUT | /api/v1/servers/:id/config | 更新客户端配置(加密存储) |
添加服务器:
{
"name": "HK-01",
"display_name": "香港节点1",
"region": "HK",
"tags": ["production", "cdn"],
"encrypted_config": "base64...",
"config_nonce": "base64..."
}
生成安装命令:
// 请求
{
"password_hash": "argon2id$...", // 用于派生 master_key
"encrypted_master_key_verify": "base64..." // 前端用 master_key 加密一段已知文本验证密码正确
}
// 响应
{
"install_command": "curl -sL https://status.example.com/install.sh | bash -s -- --token=abc123 --encrypted-config=base64...",
"agent_token": "abc123..."
}
7.7 监控与告警
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/alert-rules | 告警规则列表 |
| POST | /api/v1/alert-rules | 创建告警规则 |
| PUT | /api/v1/alert-rules/:id | 更新告警规则 |
| DELETE | /api/v1/alert-rules/:id | 删除告警规则 |
| GET | /api/v1/alert-events | 告警事件列表 |
7.8 通知渠道
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/notify-channels | 通知渠道列表 |
| POST | /api/v1/notify-channels | 添加通知渠道(配置加密存储) |
| PUT | /api/v1/notify-channels/:id | 更新通知渠道 |
| DELETE | /api/v1/notify-channels/:id | 删除通知渠道 |
| POST | /api/v1/notify-channels/:id/test | 测试通知 |
7.9 备份管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/servers/:id/backups | 备份列表 |
| POST | /api/v1/servers/:id/backups/trigger | 手动触发备份 |
| GET | /api/v1/backups/:id/download | 下载备份 |
| DELETE | /api/v1/backups/:id | 删除备份 |
7.10 探针页面
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/probe-page | 获取自己的探针页面配置 |
| PUT | /api/v1/probe-page | 更新探针页面配置 |
| GET | /probe/{slug} | 公开探针页面 (SSR) |
7.11 安装向导
首次部署时的安装流程,用于配置数据库、Redis(可选)、管理员账号。安装完成后锁定。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /install | 安装页面 (SSR) |
| GET | /api/v1/install/status | 检查系统是否已安装 |
| POST | /api/v1/install/database | 配置数据库连接 |
| POST | /api/v1/install/redis | 配置 Redis 连接(可选) |
| POST | /api/v1/install/admin | 创建管理员账号 |
| POST | /api/v1/install/complete | 完成安装,锁定安装入口 |
| GET | /api/v1/install/test-db | 测试数据库连接 |
| GET | /api/v1/install/test-redis | 测试 Redis 连接(可选) |
安装状态检查响应:
{
"installed": false, // true 表示已安装,锁定所有安装 API
"step": "database", // 当前步骤:database / redis / admin / complete
"database_type": null, // 已配置的数据库类型:postgres / sqlite
"redis_enabled": null // 是否已配置 Redis
}
配置数据库请求:
{
"type": "sqlite", // postgres 或 sqlite
// SQLite 模式无需额外参数,数据文件默认存于 ./data/statusprobe.db
// PostgreSQL 模式需要以下参数:
"host": "localhost", // PostgreSQL 参数(type=postgres 时必填)
"port": 5432,
"database": "statusprobe",
"username": "postgres",
"password": "secret",
"ssl_mode": "disable" // disable / require / verify-ca / verify-full
}
配置 Redis 请求(可选):
{
"enabled": false, // false 表示不使用 Redis,使用内存缓存
"host": "localhost", // enabled=true 时必填
"port": 6379,
"password": "", // 可选
"db": 0 // 可选,默认 0
}
创建管理员账号请求:
{
"username": "admin",
"password_hash": "argon2id$...",
"email": "admin@example.com", // 可选
"encrypted_config_key": "base64...",
"config_key_nonce": "base64..."
}
安装流程:
用户访问 /install
│
├─ GET /api/v1/install/status → 已安装? → 重定向到 /login
│
├─ 步骤 1: 选择数据库类型
│ ├─ SQLite → 直接 POST /api/v1/install/database (type=sqlite)
│ ├─ PostgreSQL → 输入连接参数 → GET /api/v1/install/test-db 测试 → POST 配置
│
├─ 步骤 2: Redis 配置(可选)
│ ├─ 跳过 → POST /api/v1/install/redis (enabled=false)
│ ├─ 配置 → 输入参数 → GET /api/v1/install/test-redis 测试 → POST 配置
│
├─ 步骤 3: 创建管理员账号
│ └─ POST /api/v1/install/admin
│
└─ 步骤 4: 完成安装
└─ POST /api/v1/install/complete
├─ 写入 config.yaml(数据库、Redis 配置)
├─ 初始化数据库表(迁移)
├─ 创建默认套餐(Free 套餐)
├─ 设置 installed=true(写入 system_settings 或配置文件)
└─ 重定向到 /login
安全要点:
- 安装完成后,所有
/api/v1/install/*API 返回 403 Forbidden installed状态存储在配置文件config.yaml或system_settings表- 安装页面仅在
installed=false时可访问 - 管理员账号创建时自动设置
role=admin
7.12 管理员接口
7.12.1 用户管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/admin/users | 用户列表 |
| PUT | /api/v1/admin/users/:id | 更新用户(禁用/启用/改角色) |
| DELETE | /api/v1/admin/users/:id | 删除用户 |
7.12.2 系统设置
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/admin/settings | 获取所有系统设置 |
| GET | /api/v1/admin/settings/:category | 按分类获取设置 (general/smtp/payment/security/telegram) |
| PUT | /api/v1/admin/settings | 批量更新系统设置 |
| PUT | /api/v1/admin/settings/:key | 更新单个设置项 |
| POST | /api/v1/admin/settings/test-smtp | 测试 SMTP 发送 |
| POST | /api/v1/admin/settings/test-payment/:channel | 测试支付通道连通性 |
| POST | /api/v1/admin/settings/test-tg-bot | 测试 TG Bot 连通性 |
| POST | /api/v1/admin/settings/set-tg-webhook | 设置 TG Bot Webhook |
批量更新设置请求:
{
"settings": [
{
"key": "site_name",
"value": "MyStatus"
},
{
"key": "smtp_host",
"value": "smtp.example.com"
},
{
"key": "smtp_password",
"value": "new_password" // is_encrypted=true 的字段,服务端用系统密钥加密后存储
},
{
"key": "captcha_enabled",
"value": "true"
}
]
}
测试 SMTP 请求:
{
"recipient": "test@example.com" // 发送测试邮件到此地址
}
测试 SMTP 响应:
{
"success": true,
"message": "测试邮件已发送",
"latency_ms": 1250
}
7.12.3 支付管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/admin/payment-orders | 支付订单列表 |
| GET | /api/v1/admin/payment-orders/:id | 订单详情 |
| POST | /api/v1/admin/payment-orders/:id/refund | 退款 |
| GET | /api/v1/admin/payment-stats | 支付统计(收入/订单数) |
7.12.4 TCPing 节点管理
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/admin/tcping-nodes | TCPing 节点列表 |
| POST | /api/v1/admin/tcping-nodes | 添加 TCPing 节点 |
| PUT | /api/v1/admin/tcping-nodes/:id | 更新节点 |
| DELETE | /api/v1/admin/tcping-nodes/:id | 删除节点 |
7.12.5 系统统计
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/v1/admin/stats | 系统概览统计 |
| GET | /api/v1/admin/stats/realtime | 实时在线 Agent 数 |
系统统计响应:
{
"total_users": 1250,
"active_users": 890,
"total_servers": 3200,
"online_servers": 2800,
"total_orders": 5600,
"total_revenue": 125000.00,
"active_subscriptions": 780
}
7.13 支付回调接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/payment/notify/alipay | 支付宝异步通知 |
| POST | /api/v1/payment/notify/wechat | 微信支付异步通知 |
| POST | /api/v1/payment/notify/stripe | Stripe Webhook |
这些接口无需 JWT 认证,但需验证第三方签名
7.14 Telegram Bot Webhook
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /api/v1/tg/webhook | TG Bot Webhook 回调(用户绑定、命令响应) |
此接口由 Telegram 服务器调用,无需 JWT 认证,需验证 Bot Token 签名
TG Bot 支持的交互:
| 用户操作 | Bot 响应 |
|---|---|
点击绑定链接 /start bind_{token} |
确认绑定成功,返回欢迎消息 |
发送 /status |
返回用户所有服务器当前状态摘要 |
发送 /help |
返回可用命令列表 |
| 告警触发时 | Bot 主动推送告警消息到用户 Chat |
8. 客户端 (Agent) 设计
agent/
├── main.go # 入口,解析参数
├── config/
│ ├── config.go # 配置管理(本地缓存 + 远程拉取)
│ └── cache.go # 本地文件缓存
├── collector/
│ ├── system.go # 系统指标采集 (CPU/Mem/Disk/Net/Load)
│ ├── temp.go # 温度采集 (CPU/GPU 温度)
│ ├── gpu.go # GPU 指标采集 (NVIDIA-SMI / rocm-smi)
│ ├── tcping.go # TCPing 检测
│ └── process.go # 进程信息采集
├── reporter/
│ ├── grpc.go # gRPC 连接管理、重连
│ └── stream.go # 上报流管理
├── backup/
│ ├── scheduler.go # 备份调度
│ ├── restic.go # Restic 封装 (init/backup/forget/prune)
│ ├── rclone.go # Rclone 桥接 (FTP/WebDAV 适配器)
│ ├── task_runner.go # 任务执行器 (folder/database/docker)
│ ├── docker.go # Docker 备份逻辑
│ ├── database.go # 数据库备份逻辑
│ ├── pre_post.go # pre/post command 执行
│ ├── repo_manager.go # 多仓库管理
│ ├── config_cache.go # 备份配置解密与缓存
│ └── uploader.go # gRPC 分片上传 (仅 local 类型)
├── crypto/
│ └── decrypt.go # 配置解密(使用 server_key)
└── selfupdate/
└── update.go # 自更新(可选)
8.2 启动流程
1. 解析命令行参数 (--token, --encrypted-config, --server-addr)
2. 用 server_key 解密 encrypted-config → 获取初始配置
3. 连接 gRPC 服务端 (TLS)
4. FetchConfig → 获取完整配置(覆盖本地缓存)
5. 启动采集协程:
- SystemCollector (按 report_interval)
- TcpingCollector (按 tcping_interval, 目标从配置获取)
- BackupScheduler (按 backup_config)
6. 建立 ReportStream 双向流
7. 监听 ServerCommand
8. 启动 Heartbeat 保活
8.3 一键安装脚本
#!/bin/bash
# install.sh
AGENT_VERSION="1.0.0"
SERVER_ADDR="${1}"
AGENT_TOKEN="${2}"
ENCRYPTED_CONFIG="${3}"
# 检测架构
ARCH=$(uname -m)
case $ARCH in
x86_64) BINARY="statusprobe-agent-amd64" ;;
aarch64) BINARY="statusprobe-agent-arm64" ;;
*) echo "Unsupported architecture"; exit 1 ;;
esac
# 下载
curl -sL "https://github.com/yourorg/statusprobe/releases/download/v${AGENT_VERSION}/${BINARY}" \
-o /usr/local/bin/statusprobe-agent
chmod +x /usr/local/bin/statusprobe-agent
# 创建配置目录
mkdir -p /etc/statusprobe
# 写入加密配置
echo "${ENCRYPTED_CONFIG}" | base64 -d > /etc/statusprobe/encrypted_config.bin
# 创建 systemd service
cat > /etc/systemd/system/statusprobe.service << EOF
[Unit]
Description=StatusProbe Agent
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/statusprobe-agent \\
--server-addr=${SERVER_ADDR} \\
--token=${AGENT_TOKEN} \\
--config-file=/etc/statusprobe/encrypted_config.bin
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable statusprobe
systemctl start statusprobe
echo "StatusProbe Agent installed and started!"
8.4 客户端资源限制
| 指标 | 限制 |
|---|---|
| 内存占用 | ≤ 30MB |
| CPU 占用 | ≤ 1% (空闲时) |
| 网络流量 | ≤ 1MB/hour (仅上报) |
| 二进制大小 | ≤ 15MB |
9. 探针页面设计
9.1 页面结构
/probe/{slug}
├── 顶部: Logo + 标题 + 描述
├── 服务器卡片网格
│ ├── 服务器名称 + 地区标签
│ ├── 状态指示灯 (绿/红/灰)
│ ├── CPU 使用率 + 温度 (如: 45% / 62℃)
│ ├── GPU 卡片 (如有GPU: 名称 + 使用率 + 温度 + 显存,如: RTX 4090 45% / 65℃ / 8G/24G)
│ ├── 内存进度条
│ ├── 磁盘进度条
│ ├── 网络速度 (↑↓)
│ ├── 运行时间
│ └── TCPing 延迟 (展开)
└── 底部: Powered by StatusProbe
9.2 主题与组件自定义
9.2.1 内置主题
所有主题由服务端预置,前端只引用主题 ID,零安全风险:
| 主题 ID | 名称 | 风格 | 预览 |
|---|---|---|---|
default |
经典 | 白底蓝调,圆角卡片,默认主题 | ☁️ |
dark |
暗夜 | 深色背景,护眼低对比 | 🌙 |
gruvbox |
Gruvbox | 暖色调复古,米色底 + 橙红强调 | 🍂 |
catppuccin |
Catppuccin Mocha | 柔和深色,粉蓝紫渐变 | 🌸 |
nord |
Nord | 北极冷色调,蓝灰白 | ❄️ |
solarized |
Solarized | 经典 Solarized,黄底 + 蓝强调 | ☀️ |
sakura |
樱花 | 粉白渐变,日系清新 | 🌸 |
cyberpunk |
赛博朋克 | 黑底霓虹,紫青黄高饱和 | 🎮 |
glass |
毛玻璃 | 半透明卡片 + 模糊背景 | 🪟 |
minimal |
极简 | 无边框无阴影,纯文字排版 | 📄 |
主题以 CSS 变量实现,切换主题只改变 CSS 变量值,不加载外部资源。
主题 CSS 变量体系:
:root[data-theme="default"] {
--sp-bg-page: #FFFFFF;
--sp-bg-card: #FAFAFA;
--sp-bg-card-hover: #F5F5F5;
--sp-text-primary: #1E1E1E;
--sp-text-secondary: #5C5C5C;
--sp-text-muted: #909090;
--sp-border: #E0E0E0;
--sp-accent: #3B82F6;
--sp-accent-hover: #2563EB;
--sp-success: #10B981;
--sp-warning: #F59E0B;
--sp-error: #EF4444;
--sp-radius: 8px;
--sp-shadow: 0 1px 3px rgba(0,0,0,0.08);
--sp-font: 'Inter', -apple-system, sans-serif;
}
用户可通过 primary_color 覆盖主题默认强调色,仅限合法 HEX 值 #[0-9a-fA-F]{6}。
9.2.2 组件显示配置
用户在后台通过开关选择显示哪些组件,配置存储在 visible_components JSONB 中:
┌──────────────────────────────────────────────────────┐
│ 探针页面配置面板 │
│ │
│ 📊 指标组件 │
│ ┌──────────────────────────────────────────────────┐│
│ │ ☑ CPU 使用率 ☑ CPU 温度 ││
│ │ ☑ GPU 使用率 ☑ GPU 温度 ☑ GPU 显存 ││
│ │ ☑ 内存使用率 ☐ 内存百分比 ││
│ │ ☑ 磁盘使用率 ☐ 磁盘百分比 ││
│ │ ☑ 网络流量 ☑ 实时网速 ││
│ │ ☑ 运行时间 ☐ 系统信息 ││
│ └──────────────────────────────────────────────────┘│
│ │
│ 🌐 TCPing │
│ ┌──────────────────────────────────────────────────┐│
│ │ ☑ 显示 TCPing ☑ 显示延迟值 ││
│ └──────────────────────────────────────────────────┘│
│ │
│ 🏷️ 其他 │
│ ┌──────────────────────────────────────────────────┐│
│ │ ☑ 显示标签 ││
│ └──────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────┘
9.2.3 页面基础配置
| 配置项 | 类型 | 限制 |
|---|---|---|
| 页面标题 | 文本 | ≤ 100 字符 |
| 页面描述 | 文本 | ≤ 500 字符 |
| Logo | URL | 必须 https:// 开头,≤ 500 字符 |
| Favicon | URL | 必须 https:// 开头,≤ 500 字符 |
| 页脚文字 | 文本 | ≤ 200 字符,纯文本无 HTML |
| 页脚链接 | URL | 必须 https:// 开头 |
| 布局列数 | 数字 | 0-6,0 自适应 |
| 可见服务器 | 多选 | 从用户已添加的服务器中勾选 |
9.2.4 安全保障
| 措施 | 说明 |
|---|---|
| 无自定义 JS | 彻底杜绝 XSS、Cookie 窃取、挖矿等攻击 |
| 无自定义 CSS | 杜绝 CSS 注入、数据外泄(url())、UI 覆盖 |
| 主题服务端预置 | 主题文件由开发者维护,用户只选 ID |
| URL 白名单 | Logo/Favicon/链接仅允许 https:// 开头 |
| 文本纯文本 | 页脚等文本区域不解析 HTML |
| primary_color 校验 | 严格匹配 #[0-9a-fA-F]{6} 正则 |
| visible_components 校验 | 服务端校验 JSONB 结构,忽略未知字段 |
9.3 实时更新
- 公开页面使用 SSE (Server-Sent Events) 推送指标更新
- 频率:每 5s 推送一次增量数据
- 优势:SSE 基于 HTTP,天然兼容代理/CDN,自动重连,单向推送无需双向通信
- 降级:SSE 不可用时,轮询 REST API
10. 告警与通知
10.1 告警流程
指标上报 → 滑动窗口评估 → 触发告警规则
│
├─→ 首次触发 → 创建 alert_event (firing) → 尝试通知
│
├─→ 持续触发 → 检查 cooldown → 跳过通知
│
└─→ 恢复正常 → 更新 alert_event (resolved) → 尝试通知恢复
通知前置条件检查:
用户触发告警
│
├─ 用户 preference_tg_notify_enabled == false → 跳过通知
│
├─ 用户 tg_chat_id == NULL (未绑定 TG) → 跳过通知
│
├─ 系统设置 tg_bot_enabled == false → 跳过通知
│
├─ 系统设置 tg_notify_enabled == false → 跳过通知
│
└─ 全部通过 → 通过系统 Bot 发送 TG 通知
10.2 Telegram 通知模板
🔴 [告警] HK-01 CPU 使用率过高
服务器: HK-01 (香港)
指标: CPU 使用率
当前值: 95.2%
阈值: > 90%
持续时间: 5 分钟
时间: 2026-06-22 10:30:00 UTC
---
🟢 [恢复] HK-01 CPU 使用率恢复正常
当前值: 45.3%
时间: 2026-06-22 10:35:00 UTC
11. 定时备份设计
11.1 技术选型:Restic
| 对比项 | Restic | Rclone |
|---|---|---|
| 增量备份 | 原生支持(内容寻址去重) | 仅同步,非真正增量 |
| 加密 | 内置 AES-256-CTR + Poly1305 | 需外层加密或依赖存储端 |
| 去重 | 块级去重,相同内容只存一份 | 无去重 |
| 一致性快照 | 原子快照,支持并发读写安全 | 直接同步文件,可能不一致 |
| 数据库支持 | pre-command 做 dump 后备份 | 需手动 dump |
| 存储后端 | S3/Local/SFTP/B2 等 | 40+ 云存储后端 |
| 资源占用 | 低,默认限速限并发 | 中等 |
| 成熟度 | 备份领域成熟 | 同步领域成熟 |
结论:选择 Restic。Restic 天然支持增量、去重、加密、原子快照,与本项目需求完全匹配。Rclone 更适合文件同步而非备份。
11.2 备份类型与策略
文件夹备份
用户配置: /data, /etc/myapp
Restic 命令: restic backup /data /etc/myapp
特点: 块级去重 + 增量,仅传输变更块
Docker 备份
1. 枚举运行中容器: docker ps --format '{{.Names}}'
2. 按用户配置过滤(容器名/标签)
3. 逐容器执行:
a. docker commit {container} {container}:backup-snapshot # 创建容器文件系统快照
b. docker save {container}:backup-snapshot | restic backup --stdin --stdin-filename {container}.tar
c. docker rmi {container}:backup-snapshot # 清理临时镜像
4. 可选: 备份 docker-compose.yml 和环境文件
restic backup /opt/docker/ --include=docker-compose*.yml --include=.env
数据库备份
1. MySQL/MariaDB:
mysqldump --all-databases --single-transaction --quick | \
restic backup --stdin --stdin-filename mysql_all.sql
2. PostgreSQL:
pg_dumpall -U postgres | \
restic backup --stdin --stdin-filename pg_all.sql
3. MongoDB:
mongodump --archive | \
restic backup --stdin --stdin-filename mongo_all.archive
4. Redis:
redis-cli BGSAVE && \
restic backup /var/lib/redis/dump.rdb
11.3 运行期间数据一致性
| 场景 | 方案 | 说明 |
|---|---|---|
| 普通文件 | Restic 原生快照 | Restic 读取文件时使用并发安全策略,读到的是快照时刻的状态 |
| 数据库 | pre-command dump | 先 dump 到 stdout 通过管道送入 restic backup --stdin,避免锁表 |
| Docker 容器 | docker commit | commit 是 CoW 快照,不暂停容器,写入时复制保证一致性 |
| SQLite 等嵌入式 | 开启 WAL 模式 | restic 备份 WAL + 主库文件,恢复后自动 replay |
核心原则:对于有状态服务,永远使用 pre-command 做一致性导出,而非直接备份运行时文件。
11.4 备份配置模型
{
"enabled": true,
"interval_hours": 6,
"max_count": 14,
"retention_policy": {
"keep_last": 7,
"keep_daily": 14,
"keep_weekly": 4,
"keep_monthly": 6
},
"repos": [
{
"name": "s3-backup",
"type": "s3",
"s3_config": {
"endpoint": "s3.amazonaws.com",
"bucket": "my-backups",
"prefix": "server-hk01",
"access_key": "AKIA...",
"secret_key": "wJalr...",
"region": "us-east-1",
"use_path_style": false
},
"backup_tasks": [ "..." ]
},
{
"name": "ftp-backup",
"type": "ftp",
"ftp_config": {
"host": "ftp.example.com",
"port": 21,
"username": "backup",
"password": "s3cret",
"path": "/backups/server-hk01"
},
"backup_tasks": [ "..." ]
},
{
"name": "sftp-backup",
"type": "sftp",
"sftp_config": {
"host": "sftp.example.com",
"port": 22,
"username": "backup",
"password": "",
"private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n...",
"private_key_password": "",
"path": "/backups/server-hk01",
"host_key": "ssh-ed25519 AAAA..."
},
"backup_tasks": [ "..." ]
},
{
"name": "webdav-backup",
"type": "webdav",
"webdav_config": {
"url": "https://dav.example.com/backups/server-hk01",
"username": "backup",
"password": "s3cret",
"tls_skip_verify": false
},
"backup_tasks": [ "..." ]
}
],
"backup_tasks": [
{
"name": "web-data",
"type": "folder",
"paths": ["/data/www", "/data/logs"],
"exclude": ["*.tmp", "*.log"],
"pre_command": "",
"post_command": ""
},
{
"name": "mysql-dump",
"type": "database",
"db_type": "mysql",
"pre_command": "mysqldump --all-databases --single-transaction -u root -p${DB_PASS}",
"stdin_filename": "mysql_all.sql",
"post_command": ""
},
{
"name": "app-containers",
"type": "docker",
"containers": ["web-app", "api-server"],
"backup_compose": true,
"compose_paths": ["/opt/docker/"]
}
]
}
整个配置 JSON 通过
server_key加密后存储在服务端servers.encrypted_config中。 Agent 启动时通过 FetchConfig 拉取加密配置 → 用server_key解密 → 缓存到本地内存和文件。 后续备份任务直接使用本地缓存,避免每次执行都请求服务端。服务端配置变更时通过 ServerCommand 通知 Agent 重新拉取并刷新缓存。
11.5 Agent 备份模块架构
agent/
├── backup/
│ ├── scheduler.go # 备份调度(定时触发)
│ ├── restic.go # Restic 封装(init/backup/forget/prune)
│ ├── rclone.go # Rclone 桥接(FTP/WebDAV 适配器)
│ ├── task_runner.go # 任务执行器(folder/database/docker 三种类型)
│ ├── docker.go # Docker 备份逻辑(commit/save/cleanup)
│ ├── database.go # 数据库备份逻辑(dump via stdin)
│ ├── pre_post.go # pre/post command 执行
│ ├── repo_manager.go # 多仓库管理
│ ├── config_cache.go # 备份配置解密与缓存
│ └── uploader.go # gRPC 分片上传(仅 local 类型需要)
11.6 备份执行流程
定时触发 / 服务端手动触发
│
├─→ 1. 拉取最新备份配置(从服务端缓存)
│
├─→ 2. 遍历 repos,逐 repo 执行
│ │
│ ├─→ 2a. 检查 repo 是否已初始化 (restic cat config)
│ │ 未初始化 → restic init (密码从配置解密获取)
│ │
│ ├─→ 2b. 遍历 backup_tasks,逐 task 执行
│ │ │
│ │ ├─→ [folder] restic backup {paths} --exclude={exclude}
│ │ │
│ │ ├─→ [database] pre_command | restic backup --stdin --stdin-filename={name}
│ │ │
│ │ ├─→ [docker] commit → save → restic backup --stdin → 清理临时镜像
│ │ │
│ │ └─→ 记录任务结果(成功/失败/大小/耗时)
│ │
│ ├─→ 2c. 执行保留策略: restic forget {policy} --prune
│ │
│ └─→ 2d. 上报备份结果到服务端 (gRPC)
│
└─→ 3. 所有 repo 完成后,汇总上报
11.7 存储后端类型
Restic 原生支持 S3/SFTP,FTP 和 WebDAV 通过 rclone serve restic 适配:
| 仓库类型 | Agent 行为 | 数据流向 | 适配方式 |
|---|---|---|---|
| S3 (推荐) | restic 直接读写 S3 | Agent → S3 | Restic 原生 rest 后端 |
| SFTP | restic 直接读写 SFTP | Agent → SFTP 服务器 | Restic 原生 sftp 后端 |
| FTP | rclone serve restic → restic 通过本地 HTTP | Agent → rclone → FTP | rclone 桥接 |
| WebDAV | rclone serve restic → restic 通过本地 HTTP | Agent → rclone → WebDAV | rclone 桥接 |
FTP/WebDAV 桥接原理:
┌──────────┐ HTTP ┌──────────────┐ FTP/WebDAV ┌────────────┐
│ Restic │ ──────────→ │ rclone serve │ ──────────────→ │ Remote │
│ backup │ localhost │ restic │ protocol │ Storage │
└──────────┘ :XXXX └──────────────┘ └────────────┘
- Agent 从解密后的配置中获取 FTP/WebDAV 参数
- 启动
rclone serve restic子进程,监听本地随机端口,配置对应的远程存储 - Restic 使用
rest:http://localhost:XXXX/repo作为仓库地址 - 所有数据经 rclone 透明传输到远程 FTP/WebDAV 存储
- rclone 进程随 Agent 退出自动清理
各存储后端配置参数(解密后明文,仅存在 Agent 内存中):
| 后端 | 必需参数 | 可选参数 |
|---|---|---|
| S3 | endpoint, bucket, prefix, access_key, secret_key | region, use_path_style, tls_skip_verify |
| SFTP | host, port, username, path | password, private_key, private_key_password, host_key |
| FTP | host, port, username, password, path | tls_enabled (FTPS) |
| WebDAV | url, username, password | tls_skip_verify |
所有参数均通过
server_key加密存储在服务端,Agent 拉取后解密并缓存到本地,仅存于内存中。Restic 仓库密码同理。
11.8 备份安全
| 项目 | 方案 |
|---|---|
| 仓库密码 | 用户在前端设置,通过 server_key 加密存服务端,Agent 拉取配置后解密使用 |
| 存储凭证 | 所有后端凭证(S3 AK/SK、FTP/SFTP 密码、WebDAV 密码、SSH 私钥等)通过 server_key 加密存服务端 |
| 数据加密 | Restic 内置 AES-256-CTR 加密,服务端和存储端均无法读取明文 |
| 传输加密 | S3: HTTPS / SFTP: SSH 加密 / FTP: 明文(建议使用 FTPS)/ WebDAV: HTTPS |
| 密码隔离 | 每台服务器的 Restic 仓库使用独立密码,互不影响 |
| 配置缓存 | Agent 解密后配置仅存内存,不落盘;进程退出后自动清除 |
| rclone 凭证 | FTP/WebDAV 凭证通过 rclone config 临时写入,使用后删除 |
11.9 资源控制
| 控制项 | Restic 参数 | 默认值 |
|---|---|---|
| CPU 限制 | --limit-cpu (通过 cgroup 或 nice) |
nice 19 |
| 网络限速 | --limit-upload (KB/s) |
0 (不限,可用户配置) |
| 并发读取 | GOMAXPROCS |
2 |
| 上传并发 | restic 内部管理 | 2 connections |
| 内存占用 | 默认 ~50MB | 低 |
Agent 在执行备份时自动降低优先级 (nice/ionice),确保不影响业务正常运行。
11.10 备份记录上报 (gRPC)
更新 BackupConfig proto 定义:
message BackupConfig {
bool enabled = 1;
int32 interval_hours = 2;
int32 max_count = 3;
string encrypted_config_json = 4; // 加密的完整备份配置 JSON(含 repos、tasks 等)
}
message BackupResult {
string agent_token = 1;
string repo_name = 2;
string task_name = 3;
bool success = 4;
string snapshot_id = 5; // Restic 快照 ID
int64 files_new = 6;
int64 files_changed = 7;
int64 data_added_bytes = 8; // 本次新增数据量
int64 total_size_bytes = 9; // 仓库总大小
int64 duration_ms = 10;
string error_message = 11;
int64 timestamp = 12;
}
Agent 完成备份后通过 ReportStream 上报 BackupResult,服务端记录到 backup_records 表。
更新 backup_records 表结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| server_id | UUID | FK → servers |
| repo_name | VARCHAR(100) | 仓库名称 |
| task_name | VARCHAR(100) | 任务名称 |
| task_type | VARCHAR(20) | folder / database / docker |
| snapshot_id | VARCHAR(64) | Restic 快照 ID |
| success | BOOLEAN | 是否成功 |
| files_new | BIGINT | 新增文件数 |
| files_changed | BIGINT | 变更文件数 |
| data_added_bytes | BIGINT | 新增数据量 |
| total_size_bytes | BIGINT | 仓库总大小 |
| duration_ms | INT | 耗时 ms |
| error_message | TEXT | 错误信息 |
| created_at | TIMESTAMP |
12. 项目目录结构
statusprobe/
├── server/ # 服务端
│ ├── cmd/
│ │ └── server/
│ │ └── main.go # 入口:加载配置、初始化依赖、启动 HTTP/gRPC
│ │
│ ├── internal/
│ │ ├── config/ # [基础] 服务端配置加载
│ │ │ ├── config.go # 配置结构体定义与加载(YAML/ENV)
│ │ │ └── validator.go # 配置校验
│ │ │
│ │ ├── mod/ # [模块化] 业务模块,每个模块自包含
│ │ │ │
│ │ │ ├── auth/ # 认证模块:注册/登录/JWT/密码重置
│ │ │ │ ├── handler.go # HTTP handler(路由注册)
│ │ │ │ ├── service.go # 业务逻辑
│ │ │ │ ├── repository.go # 数据访问
│ │ │ │ ├── model.go # 数据模型
│ │ │ │ └── router.go # 路由定义
│ │ │ │
│ │ │ ├── user/ # 用户模块:个人信息/邮箱绑定/TG绑定/偏好
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ └── router.go
│ │ │ │
│ │ │ ├── plan/ # 套餐模块:套餐CRUD/权限
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ └── router.go
│ │ │ │
│ │ │ ├── subscription/ # 订阅模块:购买/升级/兑换码
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ ├── router.go
│ │ │ │ └── upgrade.go # 套餐升级差价计算
│ │ │ │
│ │ │ ├── payment/ # 支付模块:订单/回调/多通道
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ ├── router.go
│ │ │ │ ├── alipay.go # 支付宝通道
│ │ │ │ ├── wechat.go # 微信支付通道
│ │ │ │ └── stripe.go # Stripe 通道
│ │ │ │
│ │ │ ├── server/ # 服务器模块:添加/删除/配置/安装命令
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ └── router.go
│ │ │ │
│ │ │ ├── monitor/ # 监控模块:指标存储/查询/聚合
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ ├── router.go
│ │ │ │ └── aggregator.go # 指标降采样聚合
│ │ │ │
│ │ │ ├── alert/ # 告警模块:规则/评估/事件
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ ├── router.go
│ │ │ │ └── evaluator.go # 滑动窗口评估引擎
│ │ │ │
│ │ │ ├── notify/ # 通知模块:TG通知/通知渠道
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ ├── router.go
│ │ │ │ └── dispatcher.go # 通知分发器(检查前置条件)
│ │ │ │
│ │ │ ├── backup/ # 备份模块:备份记录/恢复/触发
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ └── router.go
│ │ │ │
│ │ │ ├── probepage/ # 探针页面模块:页面配置/SSR
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ └── router.go
│ │ │ │
│ │ │ ├── install/ # 安装向导模块:数据库配置/Redis配置/管理员创建
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ └── router.go
│ │ │ │
│ │ │ ├── setting/ # 系统设置模块:SMTP/支付/TG/安全配置
│ │ │ │ ├── handler.go
│ │ │ │ ├── service.go
│ │ │ │ ├── repository.go
│ │ │ │ ├── model.go
│ │ │ │ ├── router.go
│ │ │ │ └── smtp.go # SMTP 测试发送
│ │ │ │
│ │ │ └── admin/ # 管理员模块:用户管理/统计/汇总
│ │ │ ├── handler.go
│ │ │ ├── service.go
│ │ │ ├── repository.go
│ │ │ ├── router.go
│ │ │ └── stats.go # 系统统计聚合
│ │ │
│ │ ├── grpc/ # [gRPC] 客户端通信层
│ │ │ ├── server.go # gRPC 服务注册与启动
│ │ │ ├── agent.go # AgentService 实现(ReportStream/FetchConfig/Heartbeat)
│ │ │ ├── backup.go # 备份上传流处理
│ │ │ └── interceptor.go # 认证拦截器(agent_token 校验)
│ │ │
│ │ ├── tgbot/ # [TG Bot] Telegram 机器人
│ │ │ ├── bot.go # Bot 初始化与 Webhook 处理
│ │ │ ├── bind.go # 用户绑定流程
│ │ │ └── commands.go # Bot 命令处理 (/status, /help)
│ │ │
│ │ ├── middleware/ # [中间件] HTTP 中间件
│ │ │ ├── auth.go # JWT 认证中间件
│ │ │ ├── ratelimit.go # 限流中间件
│ │ │ ├── cors.go # 跨域中间件
│ │ │ ├── admin.go # 管理员权限中间件
│ │ │ └── recovery.go # Panic 恢复中间件
│ │ │
│ │ └── scheduler/ # [定时任务] 服务端定时任务
│ │ ├── scheduler.go # 调度器
│ │ ├── subscription.go # 订阅过期检查
│ │ ├── metrics.go # 指标降采样
│ │ └── cleanup.go # 过期数据清理
│ │
│ ├── pkg/ # [公共包] 可被 internal 引用的通用工具
│ │ ├── crypto/ # 加密工具
│ │ │ ├── aes.go # AES-256-GCM 加解密
│ │ │ └── hash.go # Argon2id / HKDF / 随机令牌生成
│ │ ├── jwt/ # JWT 工具
│ │ │ └── jwt.go # RS256 Token 签发与验证
│ │ ├── response/ # 统一响应
│ │ │ └── response.go # 标准化 JSON 响应封装
│ │ ├── validator/ # 参数校验
│ │ │ └── validator.go # 通用校验规则
│ │ └── database/ # 数据库工具
│ │ ├── postgres.go # PG 连接与迁移
│ │ └── redis.go # Redis 连接与封装
│ │
│ ├── migrations/ # 数据库迁移文件
│ ├── proto/ # Protobuf 定义
│ │ └── agent.proto
│ └── go.mod
│
├── agent/ # 客户端
│ ├── cmd/
│ │ └── agent/
│ │ └── main.go
│ ├── internal/
│ │ ├── config/
│ │ ├── collector/
│ │ ├── reporter/
│ │ ├── backup/
│ │ └── crypto/
│ └── go.mod
│
├── web/ # 前端
│ ├── admin/ # 管理后台 (Vue3 + Naive UI)
│ │ ├── src/
│ │ │ ├── views/ # 页面
│ │ │ │ ├── dashboard/ # 仪表盘
│ │ │ │ ├── users/ # 用户管理
│ │ │ │ ├── plans/ # 套餐管理
│ │ │ │ ├── orders/ # 订单管理
│ │ │ │ ├── redeem/ # 兑换码管理
│ │ │ │ ├── tcping/ # TCPing 节点管理
│ │ │ │ └── settings/ # 系统设置(通用/SMTP/支付/TG/安全)
│ │ │ ├── components/ # 通用组件
│ │ │ ├── stores/ # Pinia 状态管理
│ │ │ ├── api/ # API 请求封装
│ │ │ ├── router/ # 路由配置
│ │ │ └── utils/ # 工具函数
│ │ └── ...
│ │
│ ├── dashboard/ # 用户面板 (Vue3 + Naive UI)
│ │ ├── src/
│ │ │ ├── views/
│ │ │ │ ├── overview/ # 总览
│ │ │ │ ├── servers/ # 服务器管理
│ │ │ │ ├── monitor/ # 监控详情
│ │ │ │ ├── alerts/ # 告警规则
│ │ │ │ ├── backup/ # 备份管理
│ │ │ │ ├── probepage/ # 探针页面配置
│ │ │ │ ├── subscription/ # 套餐/订阅
│ │ │ │ ├── settings/ # 账号设置/邮箱/TG绑定/偏好
│ │ │ │ └── install/ # 一键安装命令生成
│ │ │ ├── components/
│ │ │ │ ├── charts/ # 图表组件(CPU/Mem/Net 折线图)
│ │ │ │ ├── server-card/ # 服务器状态卡片
│ │ │ │ └── crypto/ # 前端加密组件(Argon2id/AES)
│ │ │ ├── stores/
│ │ │ ├── api/
│ │ │ ├── router/
│ │ │ └── utils/
│ │ └── ...
│ │
│ ├── probe/ # 探针页面 (Nuxt3 SSR)
│ │ ├── pages/
│ │ │ └── [slug].vue # 公开探针页面
│ │ ├── components/
│ │ │ ├── server-grid.vue # 服务器卡片网格
│ │ │ ├── server-card.vue # 单服务器卡片
│ │ │ ├── gpu-card.vue # GPU 信息卡片
│ │ │ └── tcping-table.vue # TCPing 结果表
│ │ ├── composables/ # 组合式函数
│ │ └── ...
│ │
│ └── shared/ # 前端共享代码
│ ├── crypto/ # 加密工具(Argon2id/HKDF/AES-256-GCM)
│ │ ├── argon2id.ts # 密码哈希
│ │ ├── hkdf.ts # 密钥派生
│ │ └── aes-gcm.ts # 配置加解密
│ ├── types/ # 共享类型定义
│ └── constants/ # 共享常量
│
├── install/
│ └── install.sh # 一键安装脚本
│
├── docs/ # 文档
└── docker/
├── docker-compose.yml
├── server.Dockerfile
└── agent.Dockerfile
12.5 后端模块化开发规范
模块结构
每个业务模块位于 internal/mod/{module}/ 下,自包含四层:
mod/{module}/
├── model.go # 数据模型:数据库表映射 struct + DTO(请求/响应结构体)
├── repository.go # 数据访问:数据库 CRUD,封装 SQL 查询,不包含业务逻辑
├── service.go # 业务逻辑:核心业务处理,调用 repository,可跨模块调用其他 service
└── handler.go # HTTP handler:参数校验、调用 service、格式化响应,不含业务逻辑
模块间依赖规则
handler → service → repository
│ │
│ └──→ 可调用其他模块的 service(通过接口注入)
│
└──→ 不直接调用其他模块的 repository
禁止:
- handler 直接调用 repository
- service 直接操作 http.Request/Response
- 循环依赖(A service 调 B service,B service 又调 A service)
接口注入示例
// mod/alert/service.go
type AlertService struct {
alertRepo repository.AlertRepository
serverSvc server.ServiceInterface // 通过接口注入,不直接依赖 server 模块实现
notifySvc notify.ServiceInterface
}
func NewAlertService(
alertRepo repository.AlertRepository,
serverSvc server.ServiceInterface,
notifySvc notify.ServiceInterface,
) *AlertService {
return &AlertService{...}
}
代码注释规范
// Package auth 提供用户认证功能,包括注册、登录、JWT 签发、密码重置等。
//
// 主要流程:
// - 注册:校验参数 → 检查用户名唯一 → 存储 password_hash + encrypted_config_key
// - 登录:校验 password_hash → 签发 JWT + 返回 encrypted_config_key
// - 修改密码:验证旧密码 → 更新 hash / 修改加密密码 → 重新加密 config_key
package auth
// UserService 处理用户相关的业务逻辑。
type UserService struct {
repo repository.UserRepository // 用户数据访问
}
// Register 注册新用户。
// 参数:
// - req: 注册请求,包含 username、password_hash、email(可选)
// 返回:
// - user: 创建的用户对象
// - err: 参数校验失败返回 ErrInvalidParam,用户名已存在返回 ErrUserExists
func (s *UserService) Register(req *RegisterRequest) (*User, error) {
// 1. 校验参数
if err := validateRegister(req); err != nil {
return nil, ErrInvalidParam
}
// 2. 检查用户名是否已存在
exists, err := s.repo.ExistsByUsername(req.Username)
if err != nil {
return nil, err
}
if exists {
return nil, ErrUserExists
}
// 3. 创建用户
user := &User{...}
if err := s.repo.Create(user); err != nil {
return nil, err
}
return user, nil
}
依赖注入与初始化
// cmd/server/main.go
func main() {
// 1. 加载配置
cfg := config.Load()
// 2. 初始化基础设施
db := database.NewPostgres(cfg.DB)
rdb := database.NewRedis(cfg.Redis)
// 3. 初始化各模块(按依赖顺序)
userRepo := user.NewRepository(db)
userSvc := user.NewService(userRepo)
userHandler := user.NewHandler(userSvc)
authSvc := auth.NewService(userRepo, cfg.JWT)
authHandler := auth.NewHandler(authSvc)
serverSvc := server.NewService(server.NewRepository(db), userSvc)
serverHandler := server.NewHandler(serverSvc)
monitorSvc := monitor.NewService(monitor.NewRepository(db, rdb))
monitorHandler := monitor.NewHandler(monitorSvc)
alertSvc := alert.NewService(alert.NewRepository(db), serverSvc, notifySvc)
alertHandler := alert.NewHandler(alertSvc)
// 4. 注册路由
r := gin.Default()
auth.RegisterRoutes(r, authHandler)
user.RegisterRoutes(r, userHandler)
server.RegisterRoutes(r, serverHandler)
// ...
// 5. 启动
r.Run(":8080")
}
13. 前端设计规范 (Termius 风格)
13.1 设计原则
遵循 Termius SSH 客户端的设计语言,核心特征:
| 原则 | 说明 |
|---|---|
| 深色终端美学 | 默认深色背景,护眼低对比度,符合 DevOps 工具定位 |
| 绿色品牌色 | 使用 #4ADE80 作为主品牌色,呼应终端/SSH 的技术感 |
| JetBrains Mono 字体 | 等宽字体用于代码、IP 地址、路径等技术文本 |
| 紧凑高效 | 信息密度优先,减少视觉噪音,紧凑间距 |
| 圆角卡片 | 统一 8px 圆角,卡片阴影 subtle |
| 状态可视化 | 使用颜色编码表示服务器状态(在线/离线/告警) |
13.2 色彩体系
深色主题 (默认):
背景: #1A1D23 (页面) / #0F1115 (侧边栏) / #22262E (卡片内)
文字: #E4E4E7 (主) / #A1A1AA (次) / #71717A (辅助)
边框: #27272A (主) / #1F1F23 (微)
品牌色: #4ADE80 (绿色 - 主色) / #22C55E (悬停) / #16A34A (按下)
状态色: #4ADE80 (在线绿) / #EF4444 (离线红) / #F59E0B (告警黄) / #3B82F6 (信息蓝)
强调色: #4ADE80 (成功) / #EF4444 (错误) / #F59E0B (警告) / #3B82F6 (信息)
浅色主题 (可选):
背景: #FFFFFF (页面) / #F4F4F5 (侧边栏) / #FAFAFA (卡片内)
文字: #18181B (主) / #52525B (次) / #A1A1AA (辅助)
边框: #E4E4E7 (主) / #F4F4F5 (微)
品牌色: #22C55E (绿色 - 主色) / #16A34A (悬停) / #15803D (按下)
状态色: #22C55E (在线绿) / #DC2626 (离线红) / #D97706 (告警黄) / #2563EB (信息蓝)
13.3 字体规范
| 用途 | 字体 | 字号 | 字重 |
|---|---|---|---|
| 标题 H1 | Inter / system-ui | 24px | 600 |
| 标题 H2 | Inter / system-ui | 20px | 600 |
| 标题 H3 | Inter / system-ui | 16px | 500 |
| 正文 | Inter / system-ui | 14px | 400 |
| 辅助文字 | Inter / system-ui | 12px | 400 |
| 代码/IP/路径 | JetBrains Mono | 13px | 400 |
| 按钮文字 | Inter / system-ui | 14px | 500 |
13.4 布局规范
| 元素 | 规范 |
|---|---|
| 侧边栏 | 宽度 240px,可折叠至 64px(图标模式),深色背景 #0F1115 |
| 顶栏 | 高度 48px,含面包屑 + 全局操作,背景 #1A1D23 |
| 内容区 | 内边距 24px,卡片间距 16px |
| 卡片 | 内边距 16px,圆角 8px,背景 #22262E,边框 #27272A |
| 表格 | 行高 40px,斑马纹 #1F2228,无边框 |
| 表单 | 标签宽度 80px,输入框高度 32px,背景 #1A1D23 |
| 按钮 | 高度 32px,圆角 6px,主按钮绿色实色,次按钮 outline |
| 标签/徽章 | 圆角 4px,高度 20px,字号 12px |
13.5 组件映射 (Naive UI)
| 场景 | Naive UI 组件 | 定制 |
|---|---|---|
| 布局 | NLayout + NLayoutSider | 深色主题,sider 深色 #0F1115 |
| 导航 | NMenu | 深色主题,缩进 16px,图标+文字 |
| 数据展示 | NDataTable | 紧凑模式 (size=small),深色主题 |
| 表单 | NForm + NInput + NSelect | 统一 size=small,深色主题 |
| 卡片 | NCard | 8px 圆角,背景 #22262E |
| 状态 | NTag + NBadge | 圆角 4px,使用状态色 |
| 图表 | ECharts (自定义) | 深色主题,品牌色 #4ADE80 |
| 反馈 | NMessage + NModal | 深色主题 |
| 加载 | NSkeleton | 深色骨架屏 |
| 图标 | NIcon + @vicons/tabler | Tabler Icons 线条风格 |
13.6 Naive UI 主题配置
// theme.ts
import { GlobalThemeOverrides } from 'naive-ui'
export const darkThemeOverrides: GlobalThemeOverrides = {
common: {
borderRadius: '8px',
fontSize: '14px',
height: '32px',
primaryColor: '#4ADE80',
primaryColorHover: '#22C55E',
primaryColorPressed: '#16A34A',
successColor: '#4ADE80',
errorColor: '#EF4444',
warningColor: '#F59E0B',
infoColor: '#3B82F6',
bodyColor: '#1A1D23',
cardColor: '#22262E',
borderColor: '#27272A',
textColorBase: '#E4E4E7',
textColor1: '#E4E4E7',
textColor2: '#A1A1AA',
textColor3: '#71717A',
},
Card: {
borderRadius: '8px',
paddingMedium: '16px',
color: '#22262E',
borderColor: '#27272A',
},
DataTable: {
thColor: '#1A1D23',
tdColor: '#22262E',
borderColor: '#27272A',
thTextColor: '#A1A1AA',
tdTextColor: '#E4E4E7',
},
Button: {
borderRadiusMedium: '6px',
heightMedium: '32px',
textColorPrimary: '#0F1115',
colorPrimary: '#4ADE80',
colorHoverPrimary: '#22C55E',
colorPressedPrimary: '#16A34A',
},
Input: {
height: '32px',
borderRadius: '6px',
color: '#1A1D23',
borderColor: '#27272A',
textColor: '#E4E4E7',
},
Menu: {
borderRadius: '6px',
itemHeight: '36px',
color: '#0F1115',
itemTextColor: '#A1A1AA',
itemTextColorHover: '#E4E4E7',
itemTextColorActive: '#4ADE80',
itemColorActive: '#1A1D23',
},
Layout: {
siderColor: '#0F1115',
headerColor: '#1A1D23',
contentColor: '#1A1D23',
},
}
export const lightThemeOverrides: GlobalThemeOverrides = {
common: {
borderRadius: '8px',
fontSize: '14px',
height: '32px',
primaryColor: '#22C55E',
primaryColorHover: '#16A34A',
primaryColorPressed: '#15803D',
successColor: '#22C55E',
errorColor: '#DC2626',
warningColor: '#D97706',
infoColor: '#2563EB',
},
Card: {
borderRadius: '8px',
paddingMedium: '16px',
},
DataTable: {
thColor: '#FAFAFA',
tdColor: '#FFFFFF',
borderColor: '#E4E4E7',
},
Button: {
borderRadiusMedium: '6px',
heightMedium: '32px',
textColorPrimary: '#FFFFFF',
colorPrimary: '#22C55E',
colorHoverPrimary: '#16A34A',
colorPressedPrimary: '#15803D',
},
Input: {
height: '32px',
borderRadius: '6px',
},
Menu: {
borderRadius: '6px',
itemHeight: '36px',
itemTextColorActive: '#22C55E',
},
}
13.7 探针页面主题
探针公开页面默认使用深色主题,与 Termius 风格保持一致:
内置主题更新:
- default → 经典白底蓝调(保留)
- dark → Termius 深色(品牌色改为 #4ADE80)
- gruvbox → 暖色调复古(保留)
- catppuccin → 柔和深色(保留)
- nord → 北极冷色调(保留)
- solarized → 经典 Solarized(保留)
- sakura → 樱花粉白(保留)
- cyberpunk → 赛博朋克(保留)
- glass → 毛玻璃(保留)
- minimal → 极简(保留)
Termius 风格 CSS 变量:
:root[data-theme="termius"] {
--sp-bg-page: #1A1D23;
--sp-bg-sidebar: #0F1115;
--sp-bg-card: #22262E;
--sp-bg-card-hover: #2A2F38;
--sp-text-primary: #E4E4E7;
--sp-text-secondary: #A1A1AA;
--sp-text-muted: #71717A;
--sp-border: #27272A;
--sp-accent: #4ADE80;
--sp-accent-hover: #22C55E;
--sp-success: #4ADE80;
--sp-warning: #F59E0B;
--sp-error: #EF4444;
--sp-info: #3B82F6;
--sp-radius: 8px;
--sp-shadow: 0 1px 3px rgba(0,0,0,0.3);
--sp-font: 'Inter', -apple-system, sans-serif;
--sp-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
}
14. 开发阶段规划
Phase 1: 基础框架
- 服务端骨架 (Gin + gRPC + GORM)
- 安装向导(数据库选型:SQLite/PostgreSQL、Redis 可选、管理员创建)
- 用户注册/登录 + JWT 认证(账号制,支持验证码/邮箱验证)
- 管理员系统设置(SMTP、支付通道、安全配置)
- 邮件服务(验证码、邮箱验证、密码重置)
- 套餐 CRUD (Admin)
- 前端基础框架搭建(Termius 风格深色主题)
Phase 2: 核心监控
- Agent 开发 (系统指标采集 + gRPC 上报)
- 服务器管理 (添加/删除/配置)
- 配置下发与缓存机制
- 探针页面基础版 (SSR + 实时数据)
Phase 3: 安全与加密
- 前端加密体系实现 (Argon2id + HKDF + AES-256-GCM)
- 加密配置存储
- 一键安装命令生成
- gRPC TLS 配置
Phase 4: 告警与通知
- 告警规则引擎
- Telegram 通知
- 告警事件管理
Phase 5: 高级功能
- 定时备份
- 兑换码系统
- 套餐升级
- 探针页面自定义 (CSS/JS/主题)
- 支付集成(支付宝/微信/Stripe)
Phase 6: 优化与上线
- 性能优化 (指标数据分区、缓存策略)
- 监控面板图表优化
- 多语言支持
- 部署与运维文档
15. 关键技术细节
15.1 指标数据存储策略
- 热数据(最近 1 小时):
- 有 Redis:Redis Sorted Set,精度 10s
- 无 Redis:内存缓存(进程内 map + LRU 淘汰),精度 10s
- 温数据(最近 7 天):PostgreSQL 按小时分区,精度 1min
- 冷数据(7 天以上):PostgreSQL 按天分区,精度 5min 聚合
- 定时任务每小时执行一次降采样
- SQLite 模式:直接存储到 server_metrics 表,配合定期清理策略(保留最近 30 天)
15.2 配置版本控制
- 每次配置变更,
config_version递增 - Agent 心跳时携带当前版本号,服务端对比后通知更新
- Agent 本地缓存配置文件,断网时使用缓存运行
15.3 套餐限制检查
// 添加服务器时检查
func (s *ServerService) AddServer(userID uuid.UUID) error {
plan, err := s.GetUserActivePlan(userID)
if err != nil {
return err
}
count, err := s.GetServerCount(userID)
if count >= plan.MaxClients {
return ErrPlanLimitExceeded
}
// ... 创建服务器
}
15.4 兑换码生成
func GenerateRedeemCode() string {
// 格式: SP-XXXX-XXXX-XXXX (16位,易读)
chars := "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" // 去除易混淆字符
code := "SP-"
for i := 0; i < 3; i++ {
if i > 0 {
code += "-"
}
for j := 0; j < 4; j++ {
code += string(chars[rand.Intn(len(chars))])
}
}
return code
}
15.5 gRPC 连接管理
- Agent 维持长连接,自动重连(指数退避:1s, 2s, 4s, ... 60s)
- 服务端检测连接断开 → 标记服务器 offline → 触发离线告警
- 双向流断开时,Agent 切换到 Heartbeat 模式保活
- 服务端支持 graceful shutdown,通知 Agent 重连
16. 部署架构
┌─────────────┐
│ Nginx │
│ (反向代理) │
└──────┬──────┘
│
┌────────────┼────────────┐
│ │ │
┌─────┴─────┐ ┌───┴────┐ ┌─────┴─────┐
│ Server │ │ Server │ │ Probe │
│ (API) │ │ (gRPC) │ │ (Nuxt3) │
│ :8080 │ │ :9090 │ │ :3000 │
└─────┬─────┘ └───┬────┘ └───────────┘
│ │
┌─────┴────────────┴─────┐
│ │
┌────┴────┐ ┌──────┴──────┐
│ PG │ │ Redis │
│ :5432 │ │ :6379 │
└─────────┘ └─────────────┘
- API 和 gRPC 可部署为多实例,通过 Nginx 负载均衡
- PostgreSQL 建议主从复制
- Redis 建议哨兵模式
- S3 使用 MinIO 自建或云服务