5/8/2026
Holdings Monitor Web - 系统架构与核心流程
一、项目概述
项目名称:Holdings Monitor Web
技术栈:Next.js 16 + TypeScript + Supabase + Vercel + OpenClaw + Tailwind CSS
核心功能:习惯打卡、飞书消息通知、持仓监控、悄悄话记录
二、系统架构
2.1 整体架构图
┌─────────────────────────────────────────────────────────────────┐
│ 前端层 (Frontend) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Habits │ │ Settings │ │ Login │ │ Whispers │ │
│ │ (习惯) │ │ (设置) │ │ (登录) │ │ (悄悄话) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
└───────┼─────────────┼─────────────┼────────────────┼───────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ API 层 (Backend) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ /api/reminder → 习惯提醒核心API │ │
│ │ /api/feishu/webhook → 飞书消息回调 │ │
│ │ /api/test-notification → 通知测试 │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────┬────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 数据层 (Database) │
│ ┌────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ habits │ │habit_records │ │system_settings │ │
│ │ (习惯表) │ │ (打卡记录表) │ │ (系统设置表) │ │
│ └────────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 外部服务 (External) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Supabase │ │OpenClaw │ │ Feishu │ │
│ │ (数据库) │ │(消息通道)│ │ (飞书通知) │ │
│ └──────────┘ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 模块划分
| 模块 | 功能职责 | 关键文件 |
|------|----------|----------|
| 习惯管理 | 习惯CRUD、打卡记录 | app/habits/, lib/habits.ts |
| 提醒系统 | 定时提醒、消息发送 | app/api/reminder/route.ts |
| 飞书集成 | 消息监听、命令处理 | app/api/feishu/webhook/route.ts, scripts/listen-feishu.sh |
| 认证系统 | 用户登录、RLS权限 | contexts/AuthContext.tsx, scripts/setup-auth-rls.ts |
| 数据访问 | 数据库操作封装 | lib/supabase.ts, lib/supabase-client.ts |
2.3 核心目录结构
holdings-monitor-web/
├── app/ # Next.js App Router
│ ├── api/ # API 路由
│ │ ├── reminder/ # 提醒核心API
│ │ └── feishu/webhook/ # 飞书回调
│ ├── habits/ # 习惯打卡页面
│ ├── settings/ # 设置页面
│ └── whispers/ # 悄悄话页面
├── lib/ # 工具库
│ ├── habits.ts # 习惯数据访问
│ ├── supabase.ts # 服务端客户端(Admin)
│ └── supabase-client.ts # 客户端(SSR)
├── scripts/ # 运维脚本
│ ├── send-reminders.sh # 发送提醒脚本
│ ├── listen-feishu.sh # 飞书消息监听
│ └── setup-cron.sh # OpenClaw Cron配置
└── supabase/ # 数据库迁移脚本
三、核心功能工作流程
3.1 习惯打卡流程
sequenceDiagram
participant User as 用户
participant Frontend as 前端
participant API as API Route
participant DB as Supabase
User->>Frontend: 打开习惯页面
Frontend->>API: GET /api/habits (获取习惯列表)
API->>DB: SELECT * FROM habits WHERE user_id = $1
DB-->>API: 返回习惯列表
API-->>Frontend: 返回习惯数据
Frontend-->>User: 展示习惯卡片
User->>Frontend: 点击打卡按钮
Frontend->>API: POST /api/reminder (action: process_reply)
API->>DB: INSERT/UPDATE habit_records
DB-->>API: 操作成功
API-->>Frontend: 返回成功状态
Frontend-->>User: 显示打卡成功
3.2 定时提醒流程
sequenceDiagram
participant Cron as OpenClaw Cron
participant Script as send-reminders.sh
participant API as /api/reminder
participant DB as Supabase
participant OpenClaw as OpenClaw
participant Feishu as 飞书
Cron->>Script: 定时触发(每小时)
Script->>API: POST action=send_all_reminders
API->>DB: SELECT * FROM system_settings WHERE openclaw_enabled=true
DB-->>API: 返回用户设置列表
loop 遍历每个用户
API->>DB: SELECT * FROM habits WHERE user_id = $1
DB-->>API: 返回用户习惯
API->>DB: SELECT status FROM habit_records WHERE date = today
DB-->>API: 返回打卡状态
API->>API: 过滤未完成且到提醒时间的习惯
API->>OpenClaw: openclaw message send
OpenClaw->>Feishu: 发送提醒消息
end
API-->>Script: 返回发送结果
Script-->>Cron: 执行完成
3.3 飞书消息处理流程
sequenceDiagram
participant User as 用户(飞书)
participant OpenClaw as OpenClaw
participant Script as listen-feishu.sh
participant API as /api/reminder
participant DB as Supabase
User->>OpenClaw: 发送消息 "/打卡 1"
Script->>OpenClaw: openclaw message read (轮询)
OpenClaw-->>Script: 返回新消息
Script->>Script: 检查是否已处理(去重)
Script->>API: POST action=process_reply, message="/打卡 1"
API->>DB: 获取用户习惯列表(按规则过滤)
DB-->>API: 返回习惯数据
API->>API: 解析命令,定位目标习惯
API->>DB: UPDATE habit_records SET status='completed'
DB-->>API: 更新成功
API-->>Script: 返回处理结果
Script->>OpenClaw: openclaw message send (回复)
OpenClaw->>User: 发送确认消息
四、小记功能(博客/笔记展示)
4.1 功能概述
小记功能用于展示用户的博客文章和笔记,支持从 GitHub 仓库同步内容,展示目录结构和最后更新时间。
4.2 GitHub 目录结构展示
MyObsidian/ # GitHub 仓库根目录
├── Projects/ # 项目文件夹
│ ├── My Blog/ # 个人博客
│ │ └── 我的站点技术栈.md # 博客文章
│ └── Holdings Monitor/ # 当前项目
│ └── 系统架构与核心流程.md
├── Tools/ # 工具笔记
│ ├── Obsidian CLI 功能分析.md
│ └── Obsidian 功能与配置指南.md
├── Goals/ # 目标管理
│ └── 目标管理指南.md
├── Whispers/ # 悄悄话(私密笔记)
│ ├── 2026-04-23-0648.md
│ └── 2026-04-23-0649.md
└── 欢迎.md # 首页欢迎文档
4.3 文件列表展示设计
根据用户界面设计,小记页面展示如下信息:
| 显示项 | 说明 | 数据源 |
|--------|------|--------|
| 文件名 | 笔记标题(不含 .md 后缀) | GitHub 文件列表 |
| 最后更新时间 | 文件最后 commit 日期 | GitHub API - last_modified |
| 目录层级 | 文件所在路径 | GitHub API - path |
4.4 时间逻辑说明
最后编辑时间的获取优先级:
-
GitHub Commit 时间(最高优先级)
- 通过 GitHub API 获取文件的最后 commit 信息
- 格式:
YYYY-MM-DD(如2026-04-29)
-
文件元数据时间(备选)
- 读取文件的
updated_at字段 - 适用于本地数据库存储的笔记
- 读取文件的
4.5 小记页面工作流程
sequenceDiagram
participant User as 用户
participant Frontend as 小记页面
participant API as GitHub API / 本地 API
participant GitHub as GitHub Repository
User->>Frontend: 访问小记页面
Frontend->>API: 请求文件列表
alt 从 GitHub 获取(博客文章)
API->>GitHub: GET /repos/{owner}/{repo}/contents/{path}
GitHub-->>API: 返回目录结构和文件信息
API-->>Frontend: 返回文件列表(含 last_modified)
else 从本地数据库获取(悄悄话)
API->>API: SELECT * FROM whispers ORDER BY updated_at DESC
API-->>Frontend: 返回笔记列表(含 updated_at)
end
Frontend-->>User: 展示文件列表(文件名 + 最后更新时间)
User->>Frontend: 点击文件
Frontend->>API: 请求文件内容
API-->>Frontend: 返回 Markdown 内容
Frontend-->>User: 渲染并展示笔记详情
五、关键技术要点
5.1 数据库设计
habits 表(习惯表): | 字段 | 类型 | 说明 | |------|------|------| | id | UUID | 主键 | | user_id | UUID | 用户ID(null表示共享习惯) | | title | VARCHAR | 习惯名称 | | description | TEXT | 描述 | | use_custom_reminder | BOOLEAN | 是否使用自定义提醒时间 | | reminder_time | TIME | 提醒时间 | | created_at | TIMESTAMP | 创建时间 | | updated_at | TIMESTAMP | 最后编辑时间(用于排序展示) |
habit_records 表(打卡记录表): | 字段 | 类型 | 说明 | |------|------|------| | id | UUID | 主键 | | habit_id | UUID | 关联习惯ID | | date | DATE | 打卡日期 | | status | VARCHAR | completed/skipped/failed | | created_at | TIMESTAMP | 创建时间 | | updated_at | TIMESTAMP | 最后编辑时间(打卡状态变更时间) |
5.2 时间处理逻辑
// UTC 转北京时间
const now = new Date();
const utcHour = now.getUTCHours();
const beijingHour = (utcHour + 8) % 24;
const beijingDate = new Date(now.getTime() + 8 * 60 * 60 * 1000);
const dateStr = beijingDate.toISOString().split('T')[0];
5.3 飞书命令格式
| 命令 | 功能 |
|------|------|
| /打卡 1 | 完成第1个待打卡习惯 |
| /打卡 完成 | 完成所有待打卡习惯 |
| /打卡 暂停 1 | 暂停第1个习惯 |
| /打卡 未完成 1 | 标记第1个习惯为未完成 |
六、部署与运维
6.1 部署架构
┌─────────────────┐
│ Vercel │ ← 前端部署(自动CI/CD)
│ Frontend │
└────────┬────────┘
│ API Requests
▼
┌─────────────────┐
│ Supabase │ ← 数据库托管
│ PostgreSQL │
└────────┬────────┘
│ Webhook
▼
┌─────────────────┐
│ OpenClaw │ ← 消息通道+定时任务
│ Cron + Message │
└────────┬────────┘
│ Send Message
▼
┌─────────────────┐
│ Feishu │ ← 飞书机器人
│ Webhook URL │
└─────────────────┘
6.2 定时任务配置
# OpenClaw Cron 配置
openclaw cron add \
--cron "0 * * * *" \
--agent main \
--message "执行命令: /Users/su/code/holdings-monitor-web/scripts/send-reminders.sh" \
--name "habit-reminder" \
--description "每小时检查并发送习惯打卡提醒" \
--no-deliver
6.3 环境变量
| 变量名 | 用途 | |--------|------| | NEXT_PUBLIC_SUPABASE_URL | Supabase 数据库地址 | | NEXT_PUBLIC_SUPABASE_ANON_KEY | 客户端访问密钥 | | SUPABASE_SERVICE_ROLE_KEY | 服务端密钥(绕过RLS) | | SUPABASE_ACCESS_TOKEN | Supabase CLI 认证令牌(执行数据库迁移用) | | NEXT_PUBLIC_APP_URL | 应用域名 | | OPENCLAW_CHANNEL_ID | OpenClaw 通道ID | | OPENCLAW_TARGET_ID | OpenClaw 目标ID |
七、核心 API 接口
7.1 /api/reminder
| Action | 功能 | 参数 |
|--------|------|------|
| send_reminder | 发送单个用户提醒 | userId |
| send_all_reminders | 批量发送所有用户提醒 | - |
| process_reply | 处理用户打卡回复 | userId, message |
| get_pending_reminders | 获取待发送提醒列表 | - |
| debug_settings | 调试系统设置 | - |
| debug_habits | 调试习惯数据 | - |
| debug_records | 调试打卡记录 | - |
八、安全与权限
8.1 RLS (行级安全)
- habits 表:用户只能访问自己创建的习惯或共享习惯(user_id IS NULL)
- habit_records 表:用户只能访问自己的打卡记录
- system_settings 表:用户只能访问自己的设置
8.2 服务端操作
所有 API Route 使用 supabaseAdmin(服务端密钥)绕过 RLS,确保:
- 定时任务能访问所有用户数据
- 飞书回调能正确处理用户命令
- 避免因权限问题导致的数据不一致
九、故障排查指南
9.1 提醒不发送
- 检查 OpenClaw Cron 状态:
openclaw cron list - 检查数据库设置:
curl -X POST /api/reminder -d '{"action": "debug_settings"}' - 检查服务器时间:
curl -X POST /api/reminder -d '{"action": "debug_time"}'
9.2 打卡不生效
- 检查习惯顺序:
curl -X POST /api/reminder -d '{"action": "get_pending_reminders"}' - 检查打卡记录:
curl -X POST /api/reminder -d '{"action": "debug_records"}' - 检查习惯数据:
curl -X POST /api/reminder -d '{"action": "debug_habits"}'
9.3 飞书消息不响应
- 检查监听脚本:
ps aux | grep listen-feishu - 检查 OpenClaw 连接:
openclaw status - 检查飞书 Webhook 配置
十、总结
核心价值:通过飞书消息驱动的习惯打卡系统,实现:
- 自动化提醒:定时检查并推送打卡提醒
- 便捷操作:通过飞书命令快速完成打卡
- 数据同步:确保多端数据一致性
- 灵活扩展:支持自定义提醒时间、多通知渠道
技术亮点:
- 使用 OpenClaw 作为消息中间件,解耦消息发送与业务逻辑
- 服务端统一使用 Admin 密钥,避免 RLS 权限问题
- 北京时间处理,确保跨时区正确运行