# 项目概述
**本文引用的文件**
- [package.json](file://package.json)
- [src/index.ts](file://src/index.ts)
- [src/command/index.ts](file://src/command/index.ts)
- [src/client/cosmoe.ts](file://src/client/cosmoe.ts)
- [src/command/handlers/start.ts](file://src/command/handlers/start.ts)
- [src/command/handlers/login.ts](file://src/command/handlers/login.ts)
- [src/command/handlers/events.ts](file://src/command/handlers/events.ts)
- [src/command/handlers/eventDetails.ts](file://src/command/handlers/eventDetails.ts)
- [src/command/handlers/bookEvent.ts](file://src/command/handlers/bookEvent.ts)
- [src/command/handlers/history.ts](file://src/command/handlers/history.ts)
- [src/command/handlers/cancel.ts](file://src/command/handlers/cancel.ts)
- [src/command/handlers/logout.ts](file://src/command/handlers/logout.ts)
- [src/scheduler/index.ts](file://src/scheduler/index.ts)
- [wrangler.jsonc](file://wrangler.jsonc)
- [tsconfig.json](file://tsconfig.json)
- [test/index.spec.ts](file://test/index.spec.ts)
## 目录
1. [引言](#引言)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排除指南](#故障排除指南)
9. [结论](#结论)
10. [附录](#附录)
## 引言
Cosmoe Bot 是一个基于 Cloudflare Workers 的 Telegram 摄影活动预约管理机器人。它通过与 Cosmoe 摄影平台的 API 集成,为用户提供便捷的活动浏览、登录认证、在线预约与取消、预约历史查询等功能,并支持定时任务推送新活动通知。项目采用 TypeScript 开发,结合 grammY 框架与 Cloudflare Workers 平台,具备高扩展性与低运维成本。
## 项目结构
项目采用按功能模块划分的目录组织方式,核心入口位于 src/index.ts,命令路由与处理器集中在 src/command 下,客户端封装在 src/client 中,定时任务逻辑位于 src/scheduler。Wrangler 作为部署与配置工具,tsconfig 提供类型检查与编译选项,test 目录包含单元与集成测试。
```mermaid
graph TB
A["src/index.ts
Worker 入口与 Webhook"] --> B["src/command/index.ts
命令注册与对话插件"]
B --> C["src/command/handlers/*.ts
命令处理器"]
A --> D["src/scheduler/index.ts
定时任务"]
C --> E["src/client/cosmoe.ts
Cosmoe API 客户端"]
F["wrangler.jsonc
部署与触发器配置"] --> A
G["tsconfig.json
TypeScript 编译配置"] --> A
H["test/index.spec.ts
测试用例"] --> A
```
图表来源
- [src/index.ts](file://src/index.ts#L1-L47)
- [src/command/index.ts](file://src/command/index.ts#L1-L110)
- [src/client/cosmoe.ts](file://src/client/cosmoe.ts#L1-L503)
- [src/scheduler/index.ts](file://src/scheduler/index.ts#L1-L88)
- [wrangler.jsonc](file://wrangler.jsonc#L1-L31)
- [tsconfig.json](file://tsconfig.json#L1-L46)
- [test/index.spec.ts](file://test/index.spec.ts#L1-L25)
章节来源
- [src/index.ts](file://src/index.ts#L1-L47)
- [src/command/index.ts](file://src/command/index.ts#L1-L110)
- [wrangler.jsonc](file://wrangler.jsonc#L1-L31)
- [tsconfig.json](file://tsconfig.json#L1-L46)
## 核心组件
- Worker 入口与 Webhook:负责初始化 Bot、注册命令菜单、设置 Webhook 回调,并在定时事件触发时执行调度逻辑。
- 命令系统与对话插件:通过 grammY 的 conversations 插件实现多步交互式登录与状态持久化;统一注册各类命令与回调。
- Cosmoe API 客户端:封装与 Cosmoe 平台的交互,包括认证、活动列表、活动详情、预约、历史、取消等接口。
- 定时任务:周期性拉取活动列表,识别新增活动并向已登录用户发送通知。
- 存储层:使用 Cloudflare KV 保存用户凭证与对话状态,确保跨实例一致性与可扩展性。
章节来源
- [src/index.ts](file://src/index.ts#L1-L47)
- [src/command/index.ts](file://src/command/index.ts#L1-L110)
- [src/client/cosmoe.ts](file://src/client/cosmoe.ts#L1-L503)
- [src/scheduler/index.ts](file://src/scheduler/index.ts#L1-L88)
## 架构总览
下图展示了从 Telegram 用户到 Cloudflare Workers、grammY、KV 存储与外部 Cosmoe API 的整体数据流与控制流。
```mermaid
sequenceDiagram
participant U as "Telegram 用户"
participant W as "Cloudflare Worker
src/index.ts"
participant CMD as "命令系统
src/command/index.ts"
participant CONV as "对话插件
conversations"
participant KV as "KV 存储
COSMOE_CREDENTIALS/COSMOE_STORAGE"
participant API as "Cosmoe API 客户端
src/client/cosmoe.ts"
participant S as "定时任务
src/scheduler/index.ts"
U->>W : 发送命令/消息
W->>CMD : 路由到对应处理器
CMD->>CONV : 进入对话如登录
CONV->>KV : 读写对话状态
CMD->>API : 调用 Cosmoe API
API-->>CMD : 返回结果
CMD-->>U : 发送回复/内联键盘
S->>API : 拉取活动列表
API-->>S : 返回活动数据
S->>KV : 读取/更新最新活动ID
S-->>U : 推送新活动通知
```
图表来源
- [src/index.ts](file://src/index.ts#L1-L47)
- [src/command/index.ts](file://src/command/index.ts#L1-L110)
- [src/client/cosmoe.ts](file://src/client/cosmoe.ts#L1-L503)
- [src/scheduler/index.ts](file://src/scheduler/index.ts#L1-L88)
## 详细组件分析
### 命令系统与对话插件
- 组件职责
- 注册命令菜单与路由:start、login、events、event_{id}、book_{event}_{slot}、history、cancel_{id}、callbackQuery 等。
- 对话插件:基于 @grammyjs/conversations,使用 @grammyjs/storage-cloudflare 将对话状态持久化至 KV。
- 环境绑定:通过 Env 接口注入 KV 命名空间与 Bot 配置。
- 关键流程
- 登录对话:交互式收集用户名与密码,调用 Cosmoe API 获取 token,存入 KV。
- 预约流程:解析 /book_{event}_{slot},校验时段余量,支持优惠券选择与二次确认。
- 取消流程:解析 /cancel_{id},弹出确认内联键盘,二次确认后调用取消接口。
- 错误处理
- 对话读写 KV 失败时记录日志并提示用户重试。
- API 调用异常时捕获并反馈错误信息。
```mermaid
flowchart TD
Start(["进入命令处理"]) --> Route{"匹配命令/正则/回调"}
Route --> |"/login"| LoginConv["进入登录对话"]
Route --> |"/events"| EventsList["获取活动列表并展示"]
Route --> |"/event_{id}"| EventDetail["获取活动详情并展示时段"]
Route --> |"/book_{event}_{slot}"| BookFlow["校验余量/优惠券并下单"]
Route --> |"/history"| History["获取并格式化历史记录"]
Route --> |"/cancel_{id}"| CancelFlow["生成确认内联键盘"]
Route --> |回调: 确认/优惠券选择| ConfirmOrCoupon["执行确认或优惠券选择"]
LoginConv --> End(["结束"])
EventsList --> End
EventDetail --> End
BookFlow --> End
History --> End
CancelFlow --> End
ConfirmOrCoupon --> End
```
图表来源
- [src/command/index.ts](file://src/command/index.ts#L1-L110)
- [src/command/handlers/login.ts](file://src/command/handlers/login.ts#L1-L75)
- [src/command/handlers/events.ts](file://src/command/handlers/events.ts#L1-L27)
- [src/command/handlers/eventDetails.ts](file://src/command/handlers/eventDetails.ts#L1-L61)
- [src/command/handlers/bookEvent.ts](file://src/command/handlers/bookEvent.ts#L1-L226)
- [src/command/handlers/history.ts](file://src/command/handlers/history.ts#L1-L107)
- [src/command/handlers/cancel.ts](file://src/command/handlers/cancel.ts#L1-L132)
章节来源
- [src/command/index.ts](file://src/command/index.ts#L1-L110)
- [src/command/handlers/login.ts](file://src/command/handlers/login.ts#L1-L75)
- [src/command/handlers/events.ts](file://src/command/handlers/events.ts#L1-L27)
- [src/command/handlers/eventDetails.ts](file://src/command/handlers/eventDetails.ts#L1-L61)
- [src/command/handlers/bookEvent.ts](file://src/command/handlers/bookEvent.ts#L1-L226)
- [src/command/handlers/history.ts](file://src/command/handlers/history.ts#L1-L107)
- [src/command/handlers/cancel.ts](file://src/command/handlers/cancel.ts#L1-L132)
### Cosmoe API 客户端
- 组件职责
- 封装认证、活动查询、详情获取、个人资料、预约、历史、取消、优惠券等 API。
- 支持手动设置凭证与自动读取 KV 凭证。
- 数据模型要点
- 认证响应包含 user_id 与 token。
- 活动详情包含多个时间段与剩余容量。
- 预约返回包含最终价格与订单号等字段。
- 错误处理
- 未认证调用抛出错误;KV 读写失败记录日志并提示用户。
```mermaid
classDiagram
class CosmoeClient {
+getToken(username, password)
+setCredentials(userId, token)
+getCredentials()
+isAuthenticated()
+getEvents()
+getEventDetail(eventId)
+getProfile()
+getMyBookings()
+bookEvent(bookingRequest)
+getAvailableCoupons(eventId)
+cancelBooking(bookingId)
+updatePaymentOrder(bookingId, orderId)
+updateBookingNote(bookingId, note)
+selfReschedule(bookingId, newSlot)
+selfTransfer(bookingId, recipient)
+changePassword(current, new)
+register(key, username, email, password, identity)
}
```
图表来源
- [src/client/cosmoe.ts](file://src/client/cosmoe.ts#L1-L503)
章节来源
- [src/client/cosmoe.ts](file://src/client/cosmoe.ts#L1-L503)
### 定时任务与新活动通知
- 组件职责
- 每分钟检查一次新活动,对比上次最大活动 ID,向所有已登录用户推送通知。
- 使用 KV 存储“最新活动ID”,保证幂等与去重。
- 关键流程
- 拉取活动列表 → 过滤新活动 → 遍历已登录用户 → 发送通知 → 更新最新活动ID。
```mermaid
flowchart TD
Tick["定时触发"] --> LoadLast["读取最新活动ID"]
LoadLast --> Fetch["拉取活动列表"]
Fetch --> Filter{"过滤新活动"}
Filter --> |有| Notify["遍历已登录用户并发送通知"]
Filter --> |无| End["结束"]
Notify --> Update["更新最新活动ID"]
Update --> End
```
图表来源
- [src/scheduler/index.ts](file://src/scheduler/index.ts#L1-L88)
- [wrangler.jsonc](file://wrangler.jsonc#L13-L17)
章节来源
- [src/scheduler/index.ts](file://src/scheduler/index.ts#L1-L88)
- [wrangler.jsonc](file://wrangler.jsonc#L13-L17)
### 存储与环境配置
- KV 命名空间
- COSMOE_CREDENTIALS:存储用户的认证凭证(user_id、token、timestamp)。
- COSMOE_STORAGE:存储调度任务的“最新活动ID”等状态。
- 环境变量
- BOT_INFO、BOT_TOKEN:Bot 基本信息与令牌。
- KV 绑定:通过 wrangler.jsonc 配置。
章节来源
- [wrangler.jsonc](file://wrangler.jsonc#L21-L30)
- [src/command/index.ts](file://src/command/index.ts#L20-L52)
- [src/command/handlers/login.ts](file://src/command/handlers/login.ts#L50-L65)
- [src/scheduler/index.ts](file://src/scheduler/index.ts#L26-L28)
## 依赖分析
- 运行时依赖
- grammY:Telegram Bot 框架,提供命令路由、回调、内联键盘等能力。
- @grammyjs/conversations:对话插件,配合 KV 实现多步交互。
- @grammyjs/storage-cloudflare:KV 适配器,用于对话状态持久化。
- 开发依赖
- wrangler:Cloudflare Workers 部署与本地开发工具。
- vitest 与 @cloudflare/vitest-pool-workers:Workers 友好的测试框架。
- typescript:类型系统与编译支持。
- 项目脚本
- dev/start:本地开发与调试。
- deploy:部署到 Cloudflare Workers。
- test:运行测试。
- cf-typegen:生成 Cloudflare 类型定义。
```mermaid
graph LR
P["package.json"] --> G["grammy"]
P --> C["@grammyjs/conversations"]
P --> K["@grammyjs/storage-cloudflare"]
P --> W["wrangler"]
P --> V["vitest"]
P --> T["typescript"]
```
图表来源
- [package.json](file://package.json#L12-L22)
章节来源
- [package.json](file://package.json#L1-L24)
## 性能考虑
- 无服务器冷启动:Cloudflare Workers 在首次访问时可能产生冷启动延迟,建议通过常驻与合理拆分任务降低影响。
- KV 读写:对话状态与用户凭证均使用 KV,注意批量读写与错误重试策略,避免阻塞主线程。
- API 调用:对 Cosmoe API 的调用应尽量减少不必要的请求,例如在预约前先获取活动详情以校验时段。
- 定时任务频率:当前每分钟触发一次,建议根据实际业务量调整 cron 表达式,避免过度轮询。
- 消息长度限制:历史查询与活动详情需遵守 Telegram 的消息长度限制,必要时截断或分条发送。
## 故障排除指南
- 登录失败
- 检查用户名/密码是否正确,确认 Cosmoe API 返回码与消息。
- 确认 KV 写入是否成功,查看日志输出。
- 预约失败
- 确认用户已登录且凭证有效。
- 检查活动时段余量与优惠券可用性。
- 查看 API 返回的错误信息并提示用户。
- 取消失败
- 确认预约状态允许取消,且未过期。
- 检查回调数据解析与 KV 凭证有效性。
- 新活动通知未送达
- 检查定时任务是否正常触发与 KV 最新活动ID是否更新。
- 确认已登录用户列表是否正确,关注用户拒收或账号异常情况。
- 测试与本地调试
- 使用 vitest 与 @cloudflare/vitest-pool-workers 运行测试。
- 通过 wrangler dev 启动本地服务,结合日志定位问题。
章节来源
- [src/command/handlers/login.ts](file://src/command/handlers/login.ts#L67-L74)
- [src/command/handlers/bookEvent.ts](file://src/command/handlers/bookEvent.ts#L114-L118)
- [src/command/handlers/cancel.ts](file://src/command/handlers/cancel.ts#L80-L84)
- [src/scheduler/index.ts](file://src/scheduler/index.ts#L81-L84)
- [test/index.spec.ts](file://test/index.spec.ts#L1-L25)
## 结论
Cosmoe Bot 通过 Cloudflare Workers 与 grammY 的组合,构建了一个轻量、可扩展、易于维护的摄影活动预约机器人。其清晰的模块划分、完善的错误处理与定时通知机制,使其能够稳定支撑日常运营与用户交互。未来可在前端界面、支付对接与更细粒度的权限控制方面进一步增强用户体验与安全性。
## 附录
### 快速开始指南
- 环境准备
- 安装依赖:使用包管理器安装项目依赖。
- 配置 Wrangler:在 wrangler.jsonc 中完善 KV 绑定、Bot 令牌与 Cron 触发器。
- 本地开发
- 启动本地服务:运行开发脚本,访问本地端口进行调试。
- 运行测试:使用测试脚本验证核心流程。
- 部署上线
- 执行部署脚本,将 Worker 部署到 Cloudflare Workers。
- 在 Telegram 中配置 Webhook 与命令菜单,完成机器人上线。
章节来源
- [package.json](file://package.json#L5-L11)
- [wrangler.jsonc](file://wrangler.jsonc#L1-L31)
- [src/index.ts](file://src/index.ts#L13-L35)
- [test/index.spec.ts](file://test/index.spec.ts#L1-L25)