尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Vibe Kanban 云端服务端架构实战:基于 Axum 与 ElectricSQL 的实时同步引擎解析
Vibe Kanban 云端服务端架构实战基于 Axum 与 ElectricSQL 的实时同步引擎解析【免费下载链接】vibe-kanbanGet 10X more out of Claude Code, Codex or any coding agent项目地址: https://gitcode.com/GitHub_Trending/vi/vibe-kanbanVibe Kanban 的remotecrate 是其托管云服务端Vibe Kanban Cloud一个由 Axum 构建的 REST API、一个由 Vite 打包的 React SPA 前端以及通过 ElectricSQL 实现的实时同步通道。本篇技术指南以 crates/remote/AGENTS.md 为骨架结合仓库源码系统讲解该服务端的架构分层、ElectricSQL 实时同步机制Shape 订阅 txid 握手、类型安全的 CRUD 路由模式MutationBuilder、认证授权、数据库迁移与类型生成管线读完你既能照葫芦画瓢搭建本地开发环境也能掌握如何为这套实时同步系统新增一张同步表的完整实操路径。一、整体架构三层服务如何协同remotecrate 不是单一进程而是三组组件协作的读路径实时化架构其拓扑如下remote-server (Axum, port 8081) ├── /v1/* REST API (CRUD auth webhooks) ├── /shape/* ElectricSQL proxy (auth-gated shape subscriptions) └── /srv/static React SPA (built by Vite, served as fallback) PostgreSQL (port 5432) └── wal_levellogical, electric_sync role with REPLICATION ElectricSQL (port 3000, internal) └── Subscribes to Postgres via logical replication, streams shapes over HTTP这套架构的核心设计原则在 crates/remote/AGENTS.md 中被反复强调写入走 REST API权威路径读取走 ElectricSQL 实时流读路径。PostgreSQL 开启wal_levellogical逻辑复制ElectricSQL 作为订阅方捕获变更再以 HTTP Shape 流的形式推送给客户端Axum 服务端则扮演网关角色既对外提供 CRUD 接口又对所有 Shape 订阅做鉴权代理。从源码看服务端启动的完整顺序在 crates/remote/src/app.rs 中清晰可见创建数据库连接池db::create_pool执行 SQLx 迁移db::migrate创建/更新electric_sync角色密码db::ensure_electric_role_password这一步必须在 ElectricSQL 启动前完成同步 Electric 发布publication列表ensure_electric_publications初始化 JWT 服务、OAuth 提供方注册表GitHub/Google、OAuth 握手与令牌校验服务按需初始化邮件Loops、R2、Azure Blob、GitHub App、billing、analytics 等可选服务组装AppState与路由树监听SERVER_LISTEN_ADDR默认0.0.0.0:8081。值得注意服务端要求必须配置至少一个认证提供方GitHub OAuth / Google OAuth / 本地账号任选其一否则app.rs会直接bail!(no OAuth providers configured)拒绝启动。二、构建与运行开发环境与 Docker 部署2.1 本地一键启动仓库根目录的 package.json 提供了完整的 remote 开发脚本# 在仓库根目录执行 pnpm run remote:dev # 让桌面客户端连接本地云服务端 export VK_SHARED_API_BASEhttp://localhost:3000 pnpm run dev其中remote:dev实际执行的是cd crates/remote docker compose --env-file .env.remote up --build ; docker compose --env-file .env.remote down -v即通过 crates/remote/docker-compose.yml 一键拉起remote-dbPostgreSQL 16显式以-c wal_levellogical启动、electricelectricsql/electric:1.4.13与remote-server三个服务。electric服务的配置中有一组关键环境变量DATABASE_URL使用electric_sync角色连接 Postgres密码来自ELECTRIC_ROLE_PASSWORD默认remoteELECTRIC_MANUAL_TABLE_PUBLISHING: true启用手动表发布只有显式调用electric_sync_table的表才会被同步ELECTRIC_FEATURE_FLAGS: allow_subqueries,tagged_subqueries允许 Shape 的 WHERE 子句中使用子查询这正是 issue 关联表按项目订阅的实现基础。2.2 完整模式与清理需要附带附件存储Azurite或 relay 隧道时可运行pnpm run remote:dev:full额外启用relay、attachmentsprofile彻底清理并删除数据库则用pnpm run remote:dev:clean该命令执行docker compose ... down -v --remove-orphans会连同数据卷一并删除。2.3 生产镜像生产构建采用多阶段 DockerNode构建前端→ Rust编译服务端→ Debian slim运行态。前端在镜像构建期间完成打包运行期由 Axum 从硬编码路径/srv/static提供静态资源。自托管时通过PUBLIC_BASE_URL与REMOTE_SERVER_PORTS0.0.0.0:3000:8081对外暴露服务。2.4 关于 billing 私有依赖billingcrate 通过vk-billingfeature 引入在自托管 Docker 构建FEATURES为空时会被从 Cargo.toml 中剥离。因此所有涉及 billing 的代码必须用#[cfg(feature vk-billing)]门控crates/remote/src/routes/mod.rs 中甚至为未启用该 feature 的情况提供了返回空路由的mod billing兜底实现。任何新代码都不得在未加 feature gate 的情况下 import billing crate。三、核心模块地图AGENTS.md 用一张表勾勒了 remote crate 的关键模块结合源码补充如下模块职责app.rs服务端引导连接池 → 迁移 → electric 角色 → JWT → OAuth → 服务 → 监听config.rsRemoteServerConfig全部由环境变量解析空字符串视为未设置state.rsAppState跨路由共享连接池、JWT、OAuth、billing、R2 等shapes.rs16 个ShapeDefinitionT常量供 ElectricSQL 同步shape_definition.rsShapeDefinition结构体、ShapeExporttrait、define_shape!宏含编译期 SQL 校验mutation_definition.rsMutationBuilder类型安全 CRUD 路由 TS 类型元数据routes/electric_proxy.rs鉴权代理把 Shape 请求转发给内部 ElectricSQLroutes/mod.rs路由树、SPA 兜底服务、all_mutation_definitions()聚合db/mod.rs连接池、迁移、ensure_electric_role_password()auth/JWT、OAuth 提供方GitHub/Google、会话中间件AppState之所以能支撑所有路由是因为它在app.rs中被一次性组装后注入 AxumRouter所有 handler 通过State(state): StateAppState提取——这也是routes/tags.rs中每个 handler 的统一取用方式。四、ElectricSQL 实时同步从 Shape 到 txid 握手4.1 工作原理四步走AGENTS.md 将同步机制归纳为四步源码完全印证Shape 定义Shape 是单表订阅 可选WHERE/columns过滤条件在 shapes.rs 中以常量形式定义属于服务端受控内容鉴权代理electric_proxy.rs 先校验组织/项目成员身份再转发 Shape 请求到内部 ElectricSQL 服务写操作create/update/delete 全部走 REST 端点返回包裹着 Postgres 事务 IDtxid的MutationResponseT乐观更新收敛前端在 Electric 流上等待该txid出现一旦出现即丢弃乐观 UI 状态。4.2 Shape 定义的结构每个 Shape 由define_shape!宏生成例如组织级与项目级的典型定义shapes.rspub const PROJECTS_SHAPE: ShapeDefinitionProject crate::define_shape!( name: PROJECTS_SHAPE, table: projects, where_clause: r#organization_id $1#, url: /shape/projects, params: [organization_id], ); pub const PROJECT_ISSUES_SHAPE: ShapeDefinitionIssue crate::define_shape!( name: PROJECT_ISSUES_SHAPE, table: issues, where_clause: r#project_id $1#, url: /shape/project/{project_id}/issues, params: [project_id], );ShapeDefinitionT的字段shape_definition.rs包括name、table、where_clause、params、url并通过PhantomDataT绑定行类型T必须实现ts_rs::TS从而让 Shape 同时具备运行时元数据与编译期类型。define_shape!宏最巧妙之处在于编译期 SQL 校验宏体内生成一个_validate()函数用sqlx::query!把table where_clause拼成真实查询进行编译期检查并把params中的每个参数绑定为uuid::Uuid::nil()占位——这意味着表名拼写错误、WHERE 引用不存在的列都会在编译阶段直接报错从源头杜绝写错 SQL 上线后才炸的经典事故。16 个 Shape 按订阅粒度分为三档组织级organization_id参数projects、notifications按user_id、organization_member_metadata、usersWHERE 用子查询id IN (SELECT user_id FROM organization_member_metadata WHERE organization_id $1)限制为组织成员项目级project_id参数tags、project_statuses、issues、workspacesowner_user_id/project_id两种、pull_requests、pull_request_issues以及 issue 关联表issue_assignees、issue_followers、issue_tags、issue_relationships均通过issue_id IN (SELECT id FROM issues WHERE project_id $1)子查询下钻到项目粒度Issue 级issue_id参数issue_comments、issue_comment_reactionscomment_id IN (SELECT id FROM issue_comments WHERE issue_id $1)。这种项目级流式下发 issue 关联数据、issue 级单独流式评论的粒度划分既保证了看板页所需的完整数据快照又避免了大评论流随看板高频刷新。4.3 代理转发客户端无法篡改的查询proxy_tableelectric_proxy.rs的转发逻辑严格遵循服务端定表、客户端只传参数值以ELECTRIC_URL为基础 URL路径固定为/v1/shape服务端写入table与where参数where即 Shape 的where_clause来自服务端常量客户端不可覆盖按顺序把params绑定为params[1]、params[2]……对应 WHERE 中的$1、$2占位符仅放行 Electric 协议白名单参数offset、handle、live、cursor、columns中的客户端传值若配置了ELECTRIC_SECRET则附带 secret携带x-vk-electric-sticky头值为会话 UUID发起请求并将响应体以流式方式透传不缓冲同时剔除Content-Encoding/Content-Length头并追加Vary: Authorization保证浏览器侧的缓存语义正确。4.4 txid 握手消除 UI 闪烁的关键所有变更类 handler 必须返回 Postgres 事务 ID这是 AGENTS.md 反复强调的硬性约定否则会导致前端乐观更新提前丢弃、随后又被 Electric 流回滚的 UI 闪烁// 路由 handler 内 let result db::issues::create_issue(pool, payload).await?; // MutationResponse 包含 pg_current_xact_id() 得到的 txid Ok(Json(MutationResponse { data: result.data, txid: result.txid }))以 routes/tags.rs 的create_tag为例先做成员权限校验ensure_project_access再做业务校验HSL 颜色格式最终TagRepository::create返回的正是MutationResponseTag。前端在 Electric 流上等到该txid之后才认为本地写操作已被服务端确认并进入稳定状态。4.5 安全边界ElectricSQL 仅限内网客户端绝不直接连 ElectricSQL所有 Shape 请求必须经过 electric_proxy.rs 的鉴权代理代理层任何授权失败返回403 Forbidden连接失败返回502 Bad GatewayShape 是服务端常量表名、WHERE、columns 全部由服务端控制客户端无法请求任意表或任意数据范围——订阅什么由 shapes.rs 的 16 个常量说了算而不是由客户端说了算。五、新增一张同步表的完整实操AGENTS.md 给出了四条操作步骤这里结合迁移与代理源码逐条展开第 1 步创建迁移。新表需要REPLICA IDENTITY FULL并纳入 Electric 发布。仓库的做法是封装electric_sync_table(p_schema, p_table)函数定义于 20251127000000_electric_support.sql内部执行ALTER TABLE %s REPLICA IDENTITY FULL并注册同步20260114000000_electric_sync_tables.sql 就是批量调用该函数的范例SELECT electric_sync_table(public, users); SELECT electric_sync_table(public, projects); -- ... 其余表 -- 子查询过滤的表额外建立索引以保障性能 CREATE INDEX IF NOT EXISTS idx_issue_assignees_issue_id ON issue_assignees(issue_id);注意ELECTRIC_MANUAL_TABLE_PUBLISHING: true意味着未调用该函数的表不会进入同步通道。第 2 步定义 Shape。在 shapes.rs 中用define_shape!宏新增常量并按订阅粒度选择organization_id/project_id/issue_id作为params。若 WHERE 涉及子查询如 issue 关联表需确保迁移中为子查询过滤列建好索引。第 3 步注册代理路由。若新 Shape 属于既有 scope 模式org/project/issue直接在 shape_routes.rs 中登记即可若需要全新 scope 模式则在 electric_proxy.rs 中新增代理路由并复用proxy_table通用转发逻辑。第 4 步返回 txid。该表的所有 mutation 路由必须返回MutationResponseT包裹的事务 ID见 4.4 节。六、Mutation 模式一套 Builder 生成路由与类型元数据所有 CRUD 路由遵循一致的MutationBuilder模式AGENTS.md 中的范式MutationBuilder::Entity, CreatePayload, UpdatePayload::new(entities) .list(list_handler) .get(get_handler) .create(create_handler) .update(update_handler) .delete(delete_handler) .build()这套 Builder 的工程价值在 mutation_definition.rs 中体现得淋漓尽致一个定义两份产物MutationBuilder既通过router()生成 Axum 子路由GET/POST /{table}与GET/PATCH/DELETE /{table}/{id}又通过definition()产出MutationDefinition元数据供 TS 生成器消费handler 签名与声明类型强绑定create/update方法要求 handler 的提取器元组实现HasJsonPayloadC/HasJsonPayloadUtrait——该 trait 只对末尾含JsonT的提取器元组实现从而保证声明的创建/更新类型与handler 实际接收的 JSON 载荷类型永远一致杜绝元数据漂移无端点用标记类型不提供 create/update 的实体分别用NoCreate/NoUpdate标记definition()根据组合生成对应的create_type: None元数据。具体落地可对照 routes/tags.rspub fn mutation() - MutationBuilderTag, CreateTagRequest, UpdateTagRequest { MutationBuilder::new(tags) .list(list_tags) .get(get_tag) .create(create_tag) .update(update_tag) .delete(delete_tag) } pub fn router() - axum::RouterAppState { mutation().router() }tags的完整 CRUD 在同一个文件内实现list_tags/get_tag先ensure_project_access校验项目成员资格create_tag/update_tag校验 HSL 颜色is_valid_hsl_color所有写操作返回MutationResponseTag。新实体照此模式实现后TS 类型由pnpm run remote:generate-types自动生成。七、认证与授权JWTauth/jwt.rs使用VIBEKANBAN_REMOTE_JWT_SECRET签名所有受保护路由统一挂载require_session中间件。注意 config.rs 中的validate_jwt_secret有硬性约束该 secret 必须是 Base64 编码解码后长度不得少于 32 字节否则启动直接报InvalidVar错误OAuthauth/provider.rsGitHub 与 Google 双提供方通过ProviderRegistry注册至少配置一个提供方含本地账号SELF_HOST_LOCAL_AUTH_EMAIL/SELF_HOST_LOCAL_AUTH_PASSWORD否则AuthConfig::from_env返回NoOAuthProviders错误。空环境变量一律视为未配置而跳过成员校验所有资源路由在访问数据库之前先做组织/项目成员资格校验。以tags为例每个 handler 都通过RequestContext来自中间件拿到当前用户再调用ensure_project_access(state.pool(), ctx.user.id, project_id)若组织/项目校验不通过则返回 403/404。路由树的组织在 routes/mod.rs 中公开路由/health、OAuth、review、billing public 等与受保护路由分别组装受保护部分整体套上require_session中间件再统一nest(/v1, ...)最后.fallback_service(spa)把所有未命中 API 的请求交给/srv/static的index.htmlSPA 路由兜底并叠加压缩、CORS、请求 ID 与追踪层。八、前端与共享类型8.1 前端packages/remote-web/前端技术栈为React 18 React Router 7 Vite Tailwind在 Docker 镜像构建期间完成打包产物从/srv/static提供VITE_APP_BASE_URL与VITE_API_BASE_URL为构建期变量直接烘焙进 JS bundle修改后必须重新构建OAuth 采用 PKCE 流程pkce.ts避免授权码拦截风险ElectricSQL Shape 全部经/shape/*代理消费前端不直接感知内部 ElectricSQL 地址。8.2 api-types跨端共享类型远程服务端与本地桌面应用共享的类型全部放在 crates/api-types/ crate 中remote与server两个 crate 都依赖它。它包含三类内容行类型Row types数据库实体的 API 表示Issue、Project、User、Workspace等请求类型Request typescreate/update 载荷CreateIssueRequest、UpdateProjectRequest等共享枚举IssuePriority、MemberRole、PullRequestStatus、NotificationType等。所有类型派生ts-rs::TS以便自动导出 TypeScript。凡是两个后端都会用到的新实体类型必须定义在 api-types而不是 remote crate 内部。8.3 类型生成管线src/bin/generate_types.rs源码见 generate_types.rs生成 shared/remote-types.ts——远程前端消费的唯一TypeScript 类型文件。运行方式pnpm run remote:generate-types # 写入 shared/remote-types.ts pnpm run remote:generate-types --check # CI 模式文件过期则非零退出生成文件包含四类内容接口声明api-types 中每个行/请求类型的::decl()输出type_decls向量逐个声明生成时统一补export前缀ShapeDefinitionT常量每个 ElectricSQL Shape 一条来源于shapes::all_shapes()形如export const PROJECT_ISSUES_SHAPE defineShapeIssue(issues, [project_id] as const, /v1/shape/project/{project_id}/issues, ...)MutationDefinitionTRow, TCreate, TUpdate常量每个 CRUD 实体一条来源于routes::all_mutation_definitions()见 routes/mod.rs当前聚合了 projects、notifications、tags、project_statuses、issues、issue_assignees、issue_followers、issue_tags、issue_relationships、issue_comments、issue_comment_reactions、pull_request_issues 共 12 个实体类型名经to_screaming_snake_case转换为TAG_MUTATION这类常量名类型辅助工具MutationRowType、MutationCreateType、MutationUpdateType用于从 mutation 定义中提取对应类型。当 api-types 新增远程前端需要的类型时把它的::decl()加进type_decls并重跑生成器即可。本地桌面应用有独立生成器crates/server/src/bin/generate_types.rs输出 shared/types.ts。九、数据库、迁移与测试迁移SQLx 管理位于 crates/remote/migrations/启动时自动执行新迁移必须以时间戳前缀命名如20260317000000_xxx.sql离线模式SQLx 的编译期查询校验需要连接真实 Postgres 或离线查询数据.sqlx/目录CI 构建用pnpm run remote:prepare-db即cd crates/remote bash scripts/prepare-db.sh生成离线数据连接池最大 10 个连接测试cargo test --manifest-path crates/remote/Cargo.tomldefine_shape!的编译期校验、HasJsonPayload的结构化约束加上 SQLx 的编译期 SQL 检查共同构成了类型错误在编译期暴露的三重防线。十、常见陷阱速查AGENTS.md 总结了五个高频坑全部有源码依据空字符串 vs 未设置Docker Compose 的${VAR:-}会产生而std::env::var()对空串返回Ok()。可选配置必须用!v.is_empty()判断config.rs 中 R2/Azure/GitHub App 的from_env全部遵循此模式否则会把未配置误判为已配置但值为空ElectricSQL 启动顺序remote-server必须先启动先跑迁移并创建electric_sync角色ElectricSQL 才能连接成功——因此 docker-compose 中electric的depends_on同时包含remote-db与remote-server且都要求condition: service_healthyBilling feature gate所有 billing 代码必须置于#[cfg(feature vk-billing)]之后见 routes/mod.rs 的兜底模块写法前端 URL 变量是构建期的VITE_*在构建时烘焙进 JS bundle改环境变量必须重新构建镜像不能靠运行时注入SPA 兜底路径硬编码前端从/srv/static提供routes/mod.rs该路径只存在于 Docker 容器内本地裸跑服务端时需自行保证该目录存在。结语Vibe Kanban 的remotecrate 示范了一套值得借鉴的托管型实时协作后端落地形态Axum 承担权威写入与鉴权网关ElectricSQL 承担只读实时分发服务端常量化的 Shape 定义 编译期 SQL 校验 txid 握手共同保证了实时性与一致性而MutationBuilder与generate_types把新增一张同步表压缩为一个迁移 一个常量 一条注册 一行 txid的最小改动面。对想要在自有产品中复刻类似实时看板/协作体验的开发者而言这份 AGENTS.md 与其源码实现就是一份可逐步对照的工程蓝图。【免费下载链接】vibe-kanbanGet 10X more out of Claude Code, Codex or any coding agent项目地址: https://gitcode.com/GitHub_Trending/vi/vibe-kanban创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

CopilotKit 实战:用 Three.js 构建可流式预览的 MCP App 三维场景服务端

CopilotKit 实战:用 Three.js 构建可流式预览的 MCP App 三维场景服务端

CopilotKit 实战:用 Three.js 构建可流式预览的 MCP App 三维场景服务端 【免费下载链接】CopilotKit The Frontend Stack for Agents & Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol 项目地址: https://gitcode.c…

📅 2026/9/11 9:33:27
使用 LlamaIndex 的 AssemblyAI 音频转录 Reader:将语音文件一键转为可检索文档

使用 LlamaIndex 的 AssemblyAI 音频转录 Reader:将语音文件一键转为可检索文档

使用 LlamaIndex 的 AssemblyAI 音频转录 Reader:将语音文件一键转为可检索文档 【免费下载链接】llama_index LlamaIndex is the leading document agent and OCR platform 项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index 本指南围绕 Llama…

📅 2026/9/11 9:33:27
STM32F407步进电机梯形加减速控制与HAL库实现

STM32F407步进电机梯形加减速控制与HAL库实现

简介:面向STM32F4系列单片机开发者的步进电机驱动工程包,特别适用于实现步进电机的梯形加减速运动与精确定位控制。包内基于STM32F407,集成四相八拍驱动、定时器PWM脉冲序列生成、加减速算法及PID速度优化思路,可在Keil或STM32Cub…

📅 2026/9/11 9:33:27
MORE NEWS

更多资讯

📰

Linux进程环境与跳转函数深度解析

1. Linux进程环境概述在Linux系统编程中,进程环境是程序执行的上下文基础。每个进程启动时都会继承父进程的环境变量,并拥有独立的地址空间、文件描述符表以及信号处理设置。理解进程环境对于编写健壮的系统级程序至关重要,特别是在需要精细控…

📰

Python无限循环发生原因与排查:从条件设计到调试技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Duix.Avatar 新增模特失败?SQLite3 布尔值报错三步修好

Duix.Avatar 新增模特失败?SQLite3 布尔值报错三步修好 【免费下载链接】Duix-Avatar 🚀 Truly open-source AI avatar(digital human) toolkit for offline video generation and digital human cloning. 项目地址: https://gitcode.com/GitHub_Trend…

📰

汽车电子MES选型:车规追溯、防错与设备集成全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

JavaWeb在线商城系统课设:Servlet+JSP+MySQL实战全解析

简介:面向JavaWeb课程设计场景的在线商城系统源码与数据库文件,专为需要完成高评分课设的计算机专业学生准备,解决选题落地难、代码不完整、环境跑不通等常见问题。压缩包共收录1197个文件,整体体积约78.45MB,类型上包…

📰

STM32F103 AB OTA双区升级:Bootloader实现与安全回滚机制详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬