投稿:CSDN / InfoQ - 技术文章(开发者视角)¶
发布平台:CSDN(https://blog.csdn.net/ )或 InfoQ(https://www.infoq.cn/ ) 建议专栏:架构 / 云计算 / 物联网 / 安全 标签:Cloudflare Workers / Hono / React 19 / D1 / TypeScript / 应急管理
用 Cloudflare 全栈开发一个合规的应急演练评估平台:技术方案与踩坑实录¶
适合读者:全栈开发者、云原生架构师、对 Cloudflare Workers 生态感兴趣的工程师 技术栈:Cloudflare Workers + Hono + React 19 + D1 + R2 + Queues + Tailwind 背景:GB/T 46792-2025 将在 2026-07-01 实施,演练评估数字化是一个真实的市场需求
一、为什么选 Cloudflare Workers?¶
开发 ToB SaaS,最头疼的不是写代码,而是部署、运维、成本。
传统方式:买服务器 → 搭 CI/CD → 配置 Nginx → 买域名 → 配 SSL → 买数据库 → 配备份
用 Cloudflare Workers:
Workers # 运行时(全球 300+ 节点)
Hono # 轻量 API 框架(类 Express,但快 10 倍)
D1 # SQLite 边缘数据库(全球复制,低延迟)
R2 # 对象存储(存放报告 PDF / 演练视频证据)
Queues # 消息队列(报告生成、邮件通知异步处理)
Tailwind # 前端样式
React 19 # 前端框架
成本:每月 10 美元以内的免费额度可以支持中小规模 SaaS 初期运行。
二、系统架构¶
2.1 分层设计¶
用户浏览器 / 微信
↓
React 19 SPA(静态资源,托管在 Cloudflare Pages)
↓ HTTPS
Cloudflare Workers(API 网关,JWT 鉴权)
↓
┌────┴────┐
│ │
Hono API Queues(异步任务)
│ │
D1 DB R2(文件存储)
2.2 数据模型(D1 Schema,SQLite)¶
核心表:
-- 租户/机构
CREATE TABLE tenants (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
plan TEXT DEFAULT 'free', -- free | pro | enterprise
created_at INTEGER
);
-- 演练评估(核心实体)
CREATE TABLE drills (
id TEXT PRIMARY KEY,
tenant_id TEXT REFERENCES tenants(id),
name TEXT,
type TEXT, -- fire | flood | earthquake | ...
date INTEGER,
procedure_mode TEXT, -- complete | simplified
status TEXT DEFAULT 'draft',-- draft | in_progress | completed
created_at INTEGER
);
-- 评估报告(最终产出)
CREATE TABLE reports (
id TEXT PRIMARY KEY,
drill_id TEXT REFERENCES drills(id),
data TEXT, -- JSON,存储 10 模块报告内容
pdf_url TEXT,
signed_at INTEGER,
created_at INTEGER
);
-- 整改跟踪
CREATE TABLE corrections (
id TEXT PRIMARY KEY,
report_id TEXT REFERENCES reports(id),
issue TEXT,
responsible TEXT,
deadline INTEGER,
status TEXT DEFAULT 'open', -- open | in_progress | closed
closed_at INTEGER
);
-- 匿名会话(简化评估入口)
CREATE TABLE anonymous_sessions (
token TEXT PRIMARY KEY,
data TEXT,
expires_at INTEGER
);
2.3 API 设计(Hono)¶
// app.ts
const app = new Hono();
// 匿名快速评估(简化程序)
app.post('/api/v1/anonymous/session', createAnonymousSession);
app.post('/api/v1/anonymous/report', generateAnonymousReport);
// 完整评估(JWT 鉴权)
app.post('/api/v1/drill', authMiddleware, createDrill);
app.get('/api/v1/drill/:id', authMiddleware, getDrill);
app.post('/api/v1/drill/:id/report', authMiddleware, generateReport);
// 报告管理
app.get('/api/v1/report/:id', authMiddleware, getReport);
app.get('/api/v1/report/:id/export/pdf', authMiddleware, exportPDF);
// 整改跟踪
app.get('/api/v1/report/:id/corrections', authMiddleware, listCorrections);
app.patch('/api/v1/correction/:id', authMiddleware, updateCorrection);
三、AI-Chat 向导的技术实现¶
AI-Chat 是本产品的核心交互创新,背后是一套状态机 + LLM 调用 + Schema 校验的设计:
3.1 对话状态机¶
Session
├── stage: "A_scene_selection" → 选择演练场景
├── stage: "B_basic_info" → 采集基础信息
├── stage: "C_quantitative" → 量化指标采集
├── stage: "D_issues_highlights" → 问题与亮点
└── stage: "E_conclusion" → 生成结论
3.2 Schema 驱动的字段映射¶
// evaluation-report-schema.json(单一真相源)
const schema = {
"report_meta": { "fields": ["org_name", "drill_date", ...] },
"quantitative_result": { "fields": ["info_report_time", "evacuation_time", ...] }
};
// 对话阶段 → 字段采集顺序
const stageFieldMap = {
"A_scene_selection": ["drill_type", "drill_scale"],
"B_basic_info": ["org_name", "drill_date", "drill_purpose"],
"C_quantitative": ["info_report_time", "evacuation_time", "evacuation_rate"],
"D_issues_highlights": ["issues[]", "highlights[]"],
"E_conclusion": ["conclusion", "grade"]
};
3.3 LLM Prompt 设计(系统提示词)¶
const SYSTEM_PROMPT = `你是应急演练评估助手,基于 GB/T 46792-2025《突发事件应急演练评估指南》引导用户完成评估数据采集。
当前会话阶段:{current_stage}
已采集字段:{collected_fields}
缺失必填字段:{required_fields}
要求:
1. 用自然语言提问,一次只问 1 个问题
2. 收到用户回答后,提取并存储对应字段
3. 不要重复询问已采集的信息
4. 如果用户信息不完整,使用"未提供"并说明对报告的影响
5. 保持对话简洁专业,贴近应急管理从业者的语言习惯
6. 当前采集数据:{partial_data}
7. 输出格式:回答文本 + 提取的 JSON 字段
国标关键条款参考:
- 5.4.2.2 定量指标优先
- 6.5.4 报告要素
- 附录E 报告体例10模块`;
四、踩坑实录¶
坑 1:D1 的 SQLite 语法限制¶
D1 是 SQLite,不是 PostgreSQL。JSON 存储要习惯用 json_extract() 而不是 ->> 操作符:
-- ✅ 正确
SELECT json_extract(data, '$.quantitative.info_report_time') FROM reports;
-- ❌ 错误(不是 PostgreSQL)
SELECT data->>'quantitative.info_report_time' FROM reports;
坑 2:Queues 的消息体大小限制¶
Cloudflare Queues 每条消息最大 256KB。报告 JSON 超过这个限制时,需要先存 R2,再把 R2 URL 发到队列:
// 大报告先存 R2
const objectKey = `reports/${reportId}.json`;
await env.R2_BUCKET.put(objectKey, JSON.stringify(reportData));
// 队列只发 R2 路径
await env.QUEUE.send({
reportId,
r2Key: objectKey,
type: 'generate_pdf'
});
坑 3:JWT Secret 不能写死在代码里¶
Wrangler 的 .dev.vars 只在本地生效,生产环境必须用 wrangler secret put:
五、性能数据¶
| 指标 | 数据 |
|---|---|
| P50 响应时间 | 28ms(Workers 全球边缘节点) |
| P99 响应时间 | 142ms |
| D1 查询延迟 | 8-15ms(边缘读取) |
| 月均费用(初期) | < $10(含 D1 5GB + R2 10GB + Workers 10M 请求) |
六、可以参考的开源项目¶
- 完整 Schema:drill-eval/evaluation-report-schema.json
- 评估报告 10 模块样板:drill-eval/评估报告样板-GBT46792对齐.md
- AI-Chat 向导设计:drill-eval/AI-Chat向导设计与字段映射.md
本文采用 CC BY-SA 4.0 许可。