在 Cloudflare Workers 上使用 SeaORM:Rust + axum + D1 代理数据库实战指南 后端数据库ORM【免费下载链接】sea-orm A powerful relational ORM for Rust项目地址https://gitcode.com/gh_mirrors/se/sea-orm点击查看免费下载本指南基于仓库中的examples/proxy_cloudflare_worker_example示例讲解如何在 Cloudflare WorkersRust 版中集成 SeaORM通过proxy代理特性驱动 Cloudflare D1基于 SQLite 的 serverless 数据库并配合 axum 搭建 HTTP 路由。读完本文你将掌握ProxyDatabaseTrait的完整实现套路、D1 值类型与 SeaORMValue的双向映射方法以及整个示例的可运行配置能够据此在自己的 Worker 项目中复刻一套完整的 SeaORM D1 读写链路。示例概览一个以 SeaORM 为核心的 Worker该示例是一个用 Rust 编写的 Cloudflare Worker它以sea-orm作为 ORM 与存储在 Cloudflare D1 中的 SQLite 数据交互以axum作为服务器框架。整个示例的文件结构非常紧凑全部逻辑集中在examples/proxy_cloudflare_worker_example/src/下的 5 个文件中lib.rsWorker 的fetch事件入口负责获取 D1 绑定并建立 SeaORM 连接d1bridge.rs核心桥接层实现ProxyDatabaseTrait让 SeaORM 认识 D1entity.rs定义posts实体模型route.rsaxum 路由与请求处理utility.rs建表工具与错误映射。与普通 SeaORM 项目最大的不同在于这里没有 MySQL/Postgres/SQLite 的原生驱动连接串而是通过Database::connect_proxy把 SeaORM 接到一个自定义的代理数据库实现上即 D1。这一点在后续源码分析中会详细展开。环境准备与快速启动README 明确了运行前提需要安装npm和cargo并建议使用最新版本的nodejs和rust本示例的Cargo.toml声明rust-version 1.85.0、edition 2024。启动开发服务器只需一条命令npx wrangler devnpx wrangler dev会读取项目根目录的wrangler.toml先执行其[build]段定义的构建命令自动安装worker-build并以 release 模式编译 Rust Worker 为 WASM然后启动本地开发服务器并暴露 D1 绑定。首次运行会自动拉取 wrangler 与构建工具需要保持网络可用。wrangler.tomlD1 绑定与构建配置示例的 wrangler.toml 是 Worker 的行为说明书全文如下compatibility_date 2026-03-29 main build/worker/shim.mjs name axum [[d1_databases]] binding D1TEST database_name axumtest # Change it if you want to use your own database database_id 00000000-0000-0000-0000-000000000000 [build] command cargo install -q worker-build worker-build --release逐项说明compatibility_dateWorker 运行时兼容性日期决定可用 API 集合main入口模块指向worker-build产出的 shim 文件build/worker/shim.mjs该文件由构建过程生成无需手写nameWorker 名称示例中为axum[[d1_databases]]声明一个 D1 数据库绑定。binding是代码中通过env.d1(...)取用的键名示例为D1TESTdatabase_name为数据库名称database_id是占位符00000000-0000-0000-0000-000000000000实际部署前必须替换为你自己的 D1 数据库 ID[build]worker-build --release将 Rust 代码编译成 Worker 可加载的 WASM 模块cargo install -q worker-build保证构建工具就绪首次较慢后续有缓存。Cargo.toml让 SeaORM 进入代理模式的关键特性examples/proxy_cloudflare_worker_example/Cargo.toml 中最关键的是 SeaORM 的依赖声明sea-orm { path ../../, default-features false, features [ macros, proxy, with-uuid, with-chrono, with-json, debug-print, ] }proxy本示例的核心开关。它启用Database::connect_proxy与ProxyDatabaseTrait相关 API让 SeaORM 可以对接任意实现了该 trait 的“代理数据库”而不是内置驱动macros提供DeriveEntityModel等派生宏with-uuid/with-chrono/with-json分别为uuid、chrono、serde_json类型实现Value转换实体中的uuid与chrono::Utc::now()字段依赖它们debug-print开启 SQL 调试输出便于在 Worker 控制台观察生成的 SQL。其余依赖与角色的对应关系worker { version 0.7.5, features [http, axum, d1] }Cloudflare Workers Rust 运行时绑定d1feature 提供D1Database类型axum { version 0.8, default-features false, features [macros] }服务器框架tower-service让Router能作为Service被fetch事件调用wasm-bindgen及其 futuresWASM 环境下与 JS 值互操作的基础设施crate-type [cdylib]编译为 WASM 模块所需的库类型。Worker 入口从 fetch 事件到数据库连接src/lib.rs 是请求的起点#[event(fetch)] async fn fetch( req: HttpRequest, env: Env, _ctx: Context, ) - Resultaxum::http::Responseaxum::body::Body, worker::Error { console_error_panic_hook::set_once(); // https://developers.cloudflare.com/d1/worker-api/ let d1 env.d1(D1TEST)?; let db crate::d1bridge::connect_d1(d1) .await .map_err(|e| worker::Error::from(e.to_string()))?; console_log!(Connected to database); Ok(route::router(db).call(req).await?) }执行流程可以拆成四步#[event(fetch)]注册 Worker 的请求处理入口通过env.d1(D1TEST)取出wrangler.toml中声明的 D1 绑定调用d1bridge::connect_d1将其转换为 SeaORM 的DatabaseConnection这一步是整个示例的技术核心构造 axum 路由并调用router(db).call(req)把请求交给 axum 处理返回axum::http::Response。注意console_log!与console_error_panic_hook的使用WASM 环境没有标准输出所有日志都需要通过 Worker 的控制台 API 输出这也是调试 SeaORM 生成 SQL 的主要途径配合debug-print特性。核心桥接层实现 ProxyDatabaseTrait 连接 D1src/d1bridge.rs 是理解整个示例的关键。它先用Database::connect_proxy建立连接struct D1(ArcD1Database); pub async fn connect_d1(d1: D1Database) - ResultDatabaseConnection, DbErr { Database::connect_proxy(DbBackend::Sqlite, Arc::new(Box::new(D1(d1.into())))).await }connect_proxy接收两个参数后端类型这里声明为DbBackend::Sqlite因为 D1 基于 SQLite和ArcBoxdyn ProxyDatabaseTrait形式的代理实现。从 src/database/mod.rs 的源码可以看到该函数按DbBackend分别分发到ProxyDatabaseConnector::connect三种后端MySql/Postgres/Sqlite都受支持意味着 Proxy 模式不限于 D1。随后示例为D1实现了ProxyDatabaseTrait的两个必需方法query查询结果转换async fn query(self, statement: Statement) - ResultVecProxyRow, DbErr { let d1 Arc::clone(self.0); let values map_values(statement); let sql statement.sql; worker::send::SendFuture::new(async move { let res d1.prepare(sql).bind(values)?.all().await?; if let Some(message) res.error() { anyhow::bail!(message.to_string()); } let rows res.results::serde_json::Value()?; anyhow::Ok(rows.into_iter().map(json_to_proxy_row).collect()) }) .await .map_err(|e| DbErr::Exec(RuntimeErr::Internal(e.to_string()))) }要点d1.prepare(sql).bind(values)?.all()是 D1 的预编译查询res.error()用于显式检查 D1 返回的错误信息查询结果以serde_json::Value形式取出后通过json_to_proxy_row转成 SeaORM 的ProxyRow内部是一个BTreeMapString, Value。execute写操作与影响行数async fn execute(self, statement: Statement) - ResultProxyExecResult, DbErr { let d1 Arc::clone(self.0); let values map_values(statement); let sql statement.sql; worker::send::SendFuture::new(async move { let meta d1.prepare(sql).bind(values)?.run().await?.meta()?; let last_insert_id meta.as_ref().and_then(|m| m.last_row_id).unwrap_or(0) as u64; let rows_affected meta.and_then(|m| m.rows_written).unwrap_or(0) as u64; anyhow::Ok(ProxyExecResult { last_insert_id, rows_affected }) }) .await .map_err(|err| DbErr::Conn(RuntimeErr::Internal(err.to_string()))) }execute面向 INSERT/UPDATE/CREATE 等写语句从 D1 的meta()中取出last_row_id自增主键与rows_written受影响行数封装为ProxyExecResult返回。这两个值正是 SeaORM 执行插入后回填自增主键、以及rows_affected判断所依赖的数据。值得留意的是整个 D1 调用被包裹在worker::send::SendFuture::new(...)中这是为了让非Send的 WASM Future 满足 SeaORM 内部对 FutureSend约束的要求是编写 Worker 代理时的必要适配。值映射SeaORM Value 与 D1 JS 值的双向转换SeaORM 的Statement携带Values(VecValue)而 D1 的bind需要 JS 值JsValue。map_values完成了这一转换src/d1bridge.rs其映射规则值得逐类核对布尔与字符Value::Bool/Value::Char直接转换浮点Float、Double经from_f64转换有符号整数BigInt转成字符串避免精度丢失Int/SmallInt/TinyInt直接转换无符号整数BigUnsigned同样转字符串其余直接转换字符串与 JSONString、Json转成字符串形式字节Bytes被格式化为 SQLite 十六进制字面量X...时间类型ChronoDate、ChronoDateTime等 5 种 chrono 类型统一转为字符串其他/空值回退为JsValue::NULL。反向转换由json_to_proxy_row完成D1 返回的 JSON 对象中布尔映射为Value::Bool数字按i64/u64/f64分别映射为BigInt/BigUnsigned/Double字符串映射为Value::String。这套双向映射保证了 SeaORM 的类型系统与 D1 的 JSON 结果集能无缝对接。实体模型与 axum 路由一条完整的读写链路src/entity.rs 定义了一张简单的posts表#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Deserialize, Serialize)] #[sea_orm(table_name posts)] pub struct Model { #[sea_orm(primary_key)] pub id: i64, pub title: String, pub text: String, }src/route.rs 中AppState用Arc包装了DatabaseConnection并注册了两个路由Router::new() .route(/, get(handler_get)) .route(/generate, get(handler_generate)) .with_state(state)GET /先ensure_schema确保表存在再用Entity::find().all(state.db)查询全部记录最后serde_json::to_string序列化返回GET /generate构造ActiveModel插入一行数据id使用NotSet交给数据库自增title使用当前 UTC 时间的 RFC3339 字符串text使用uuid::Uuid::new_v4()生成的新 UUID然后insert(state.db)落库。这段代码直接复用了 SeaORM 标准的EntityTrait与ActiveModelTraitAPI——这也正是 Proxy 模式的魅力所在一旦connect_d1建立了连接上游业务代码与使用 MySQL/SQLite 驱动的 SeaORM 项目别无二致。自动建表与错误处理src/utility.rs 提供了两个辅助能力pub async fn ensure_schema(db: DatabaseConnection) - Result(), DbErr { let backend db.get_database_backend(); let stmt Schema::new(backend) .create_table_from_entity(crate::entity::Entity) .if_not_exists() .to_owned(); db.execute(stmt).await?; Ok(()) }ensure_schema借助 SeaORM 的Schema构建器根据实体定义生成CREATE TABLE IF NOT EXISTS posts (...)并通过db.execute执行省去了手工维护建表 SQL 的麻烦。db.get_database_backend()返回DbBackend::Sqlite因此生成的 SQL 是 SQLite 方言与 D1 兼容。map_error则把任意错误统一映射为(StatusCode::INTERNAL_SERVER_ERROR, String)并输出到 Worker 控制台。源码注释特别提醒这是为了示例简洁而采用的简化做法生产环境更推荐实现IntoResponsetrait 来做结构化错误响应。从源码理解 Proxy 连接的本质为了更清晰地理解“代理数据库”在 SeaORM 内部如何工作可以对照 src/driver/proxy.rs 中的ProxyDatabaseConnection实现。它把 SeaORM 执行层的调用原样转发给用户提供的ProxyDatabaseTraitexecute(statement)→ 调用proxy.execute(statement)并包装为ExecResultquery_one/query_all→ 调用proxy.query(statement)将返回的ProxyRow包装为QueryResultQueryResultRow::Proxybegin/commit/rollback→ 透传给proxy的对应方法ping→ 透传给proxy.ping()。也就是说SeaORM 的DatabaseConnection与 D1 之间唯一的契约就是ProxyDatabaseTrait的query、execute两个必需方法外加可选的 begin/commit/rollback/ping。任何能实现这两个方法的存储后端——无论是 D1、GlueSQL 还是其他自定义存储——都能以同样的方式接入 SeaORM 的查询构建器、实体加载器和事务机制。仓库中的 proxy_gluesql_example 就是同一思路的另一实例ProxyDatabaseTrait自身的单元测试则位于 src/database/proxy.rs。部署与后续扩展建议本地npx wrangler dev验证通过后正式部署前需要完成两件事在wrangler.toml的[[d1_databases]]中替换真实的database_id可通过npx wrangler d1 create database_name创建并获取并同步修改database_name用npx wrangler deploy发布 Worker该命令同样会先走[build]流程。在此基础上可以继续扩展的方向包括在map_values中补充更多Value变体的映射如日期时间带时区之外的细分类型在ProxyDatabaseTrait中实现事务方法以支持 SeaORM 事务 API或参照本示例的结构为其他基于 WASM 的边缘数据库实现代理桥接。无论哪种方向d1bridge.rs 提供的“实现 trait → connect_proxy → 复用全套 ORM API”三步走模式都是可复用的通用范本。赞分享后端数据库ORM【免费下载链接】sea-orm A powerful relational ORM for Rust项目地址https://gitcode.com/gh_mirrors/se/sea-orm点击查看免费下载相关推荐EmDash Cloudflare Demo 实战指南在 Workers D1 上运行 Astro 驱动的全栈 CMSEmDash Cloudflare Demo 实战指南在 Workers D1 上运行 Astro 驱动的全栈 CMS 本指南以仓库中的 demos/clCMS后端前端插件系统Cloudflare Workers 测试Miniflare 中模拟 D1 数据库的完整指南Cloudflare Workers 测试Miniflare 中模拟 D1 数据库的完整指南 Miniflare 是 Cloudflare 官方提供的 Wor文档在 Cloudflare Workers 上托管 Rivet Actorsrivetkit/cloudflare-workers 实战指南在 Cloudflare Workers 上托管 Rivet Actorsrivetkit/cloudflare workers 实战指南 Rivet Ac后端AI Agent人工智能流程编排WebSocket上一篇终极指南Theatre.js 缓存策略与持久化机制深度解析下一篇distilbert-base-german-cased vs 原版BERT为什么轻量级模型更适合生产环境创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考