
Documenso 架构解析单体仓库布局、三套 API 体系与可插拔 Provider 设计【免费下载链接】documensoThe Open Source DocuSign Alternative.项目地址: https://gitcode.com/GitHub_Trending/do/documensoDocumenso 是一个开源的电子文档签署平台其代码库采用 npm workspaces Turborepo 组织的单体仓库monorepo通过一个 Hono 服务端同时承载 UI、REST API、tRPC API 与后台任务。读完本文你将掌握 Documenso 的整体分层结构、三套 API 的路由挂载方式、背景任务系统的设计以及存储/签署/邮件/任务四类可替换 Provider 的选型机制与对应环境变量足以作为深入阅读源码或二次开发的导航地图。一、总体架构一个 Hono 服务承载全部入口架构文档 ARCHITECTURE.md 给出的核心视图是整个应用只有一个主应用包documenso/remixReact Router v7 Hono 服务端口 3000其内部按路径前缀分流到不同的处理层┌─────────────────────────────────────────────────────────────────────────────┐ │ Remix App (Hono Server) │ │ apps/remix │ ├─────────────┬─────────────┬─────────────┬─────────────┬─────────────────────┤ │ /api/v1/* │ /api/v2/* │ /api/trpc/* │ /api/jobs/* │ React Router UI │ │ (ts-rest) │ (tRPC) │ (tRPC) │ (Jobs API) │ │ ├─────────────┴─────────────┴─────────────┴─────────────┴─────────────────────┤ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │ │ │ api │ │ trpc │ │ lib │ │ email │ │ signing │ │ │ │ (REST) │ │ (RPC) │ │ (CORE) │ │ │ │ │ │ │ └─────────┘ └─────────┘ └────┬────┘ └─────────┘ └─────────────────┘ │ │ ┌──────────────────┼──────────────────┐ │ │ ┌────▼────┐ ┌─────▼─────┐ ┌─────▼─────┐ │ │ │ Storage │ │ Jobs │ │ PDF │ │ │ │ Provider │ │ Provider │ │ Signing │ │ │ └────┬────┘ └─────┬─────┘ └─────┬─────┘ │ │ │ │ │ │ └──────────────┼──────────────────┼──────────────────┼────────────────────────┘ ┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐ │ Database │ │ Inngest/ │ │ Google KMS/ │ │ S3 │ │ Local │ │ Local │ └─────────────┘ └─────────────┘ └─────────────┘这个视图的关键信息是业务逻辑集中在documenso/libCORE中而存储、后台任务、PDF 签署三类外部能力都抽象为 Provider底层实现可以是数据库/S3、Inngest/本地队列、Google KMS/本地证书——这一可插拔设计是整个代码库最重要的架构决策后文展开。从源码看服务入口 apps/remix/server/main.js 使用hono/node-server的serve启动默认端口 3000可被PORT覆盖并通过hono-react-router-adapter把 Hono 的 Hono 应用与 React Router 构建产物接在一起静态资源从build/client目录提供带哈希的资源文件使用immutable强缓存。同一文件中还处理了NEXT_PUBLIC_BASE_PATH子路径部署使 Hono 路由、Vitebase与 React Routerbasename三者保持一致。二、Monorepo 布局apps 与 packages 的分工应用apps/包说明端口documenso/remix主应用React Router (Remix) Hono 服务3000documenso/openpage-api公开分析数据 API3003documenso/docs文档站点3004核心包packages/包说明documenso/lib核心业务逻辑server-only / client-only / universal 三层documenso/trpctRPC API 层带 OpenAPI 支持API V2documenso/apiREST API 层基于 ts-restAPI V1documenso/prisma数据层Prisma ORM Kyselydocumenso/uiUI 组件库Shadcn Radix Tailwinddocumenso/email邮件模板与发信器React Emaildocumenso/auth认证Arctic 实现的 OAuth、WebAuthn/Passkeysdocumenso/signingPDF 签署Local P12、Google Cloud KMSdocumenso/ee企业版功能documenso/assets静态资源支撑包包说明documenso/app-testsE2E 测试Playwrightdocumenso/tailwind-config共享 Tailwind 配置documenso/tsconfig共享 TypeScript 配置其中documenso/lib内部分层非常明确server-only/放服务端业务逻辑用户、模板、收件人、Webhook 等约 40 个子域client-only/放浏览器侧工具与 hooksuniversal/放两端共享代码如上传、加密、ID 生成。这个划分与绝不让服务端代码混进客户端 bundle的约束一一对应。三、技术栈一览类别技术前端React、React Router v7Remix服务端Hono数据库PostgreSQL 15、Prisma、KyselyAPItRPC、ts-rest、OpenAPI样式Tailwind CSS、Radix UI、Shadcn UI认证ArcticOAuth、WebAuthn/Passkeys邮件React Email、Nodemailer任务Inngest / 本地BullMQ/Redis 可选存储S3 兼容 / 数据库PDFlibpdf/core、pdfjs-disti18nLingui构建Turborepo、Vite测试Playwright需要说明一个版本细节ARCHITECTURE.md 中写的是 React 18但根 package.json 当前依赖为react: ^19.2.7且要求 Node22.0.0、npm11.11.0实际以仓库依赖为准文档表格中的React 18应视为早期标注。四、API 架构三套并行的对外接口Documenso 同时维护三套 API全部挂载在同一个 Hono 应用上这一点可以从路由挂载文件 apps/remix/server/router.ts 得到逐行印证。API V1已弃用但维护中位置packages/api/v1/框架ts-rest契约式 REST挂载点/api/v1/*认证API TokenBearer 头路由遵循 RESTful 风格GET/POST/DELETE /api/v1/documents/*GET/POST/DELETE /api/v1/templates/*Recipients 与 fields 嵌套在 documents 之下在 router.ts 中可以看到它被挂载为独立的 Hono 子应用app.route(/api/v1, tsRestHonoApp)tsRestHonoApp来自 packages/api/hono.ts且 v1 与 v2 各自独立配置了 CORS 与数据库限流中间件。API V2当前版本位置packages/trpc/server/框架tRPC trpc-to-openapi挂载点/api/v2/*与/api/v2-beta/*认证API Token 或 Session Cookie路由采用基于 action 的命名风格/api/v2/document/*、/api/v2/template/*、/api/v2/envelope/*多文档信封、/api/v2/folder/*等。router.ts 中这段代码展示了它完整的挂载方式GET /api/v2/openapi.json直接返回 OpenAPI 文档openApiDocument来自 packages/trpc/server/open-api.ts先挂载一个downloadRoute子路由注释说明这是为了遮蔽tRPC 定义的下载路由因为 trpc-to-openapi 不支持其返回类型——这是一个值得注意的框架适配细节最后用openApiTrpcServerHandler位于 apps/remix/server/trpc/hono-trpc-open-api.ts处理所有/api/v2/*请求/api/v2-beta/*则以isBeta: true复用同一处理器。路由组织方式packages/trpc/server/按业务域拆成若干 router 目录当前仓库实际包含可对照 ARCHITECTURE.md 的结构图packages/trpc/server/ ├── document-router/ ├── template-router/ ├── envelope-router/ ├── recipient-router/ ├── field-router/ ├── folder-router/ ├── webhook-router/ ├── team-router/ ├── organisation-router/ ├── embedding-router/ └── ...内部 tRPC API前端专用挂载点/api/trpc/*用途前端页面与后端通信认证基于 Sessionrouter.ts 中由reactRouterTrpcServer处理该前缀并有独立的 trpc 限流。也就是说外部机器调用走 v1/v2人类用户在 UI 里操作走/api/trpc/*三者共用同一个进程但拥有不同的认证方式与独立的限流策略。五、后台任务系统定义与实现分离Documenso 把发邮件、文档盖章seal、Webhook 回调等异步操作统一抽象为任务Job┌─────────────────┐ ┌───────────────────────────────────────┐ │ triggerJob() │────▶│ Job Provider │ │ │ │ ┌─────────────┬─────────────────┐ │ │ - name │ │ │ Inngest │ Local │ │ │ - payload │ │ │ (Cloud) │ (Database) │ │ └─────────────────┘ │ └─────────────┴─────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ │ │ │ Job Handler │ │ │ │ (async processing) │ │ │ └─────────────────────┘ │ └───────────────────────────────────────┘位置Provider 实现在packages/lib/jobs/client/任务定义在packages/lib/jobs/definitions/调度服务端启动时调用jobsClient.startCron()启动定时调度见 router.ts 注释Inngest Provider 由云端自行处理 cron本地为空操作对外接口/api/jobs/*通过jobsClient.getApiHandler()暴露router.ts配合根 package.json 中的inngest:dev脚本inngest dev -u http://localhost:3000/api/jobs即可用 Inngest CLI 本地调试从 packages/lib/jobs/client/ 的目录结构看三个 Provider 各自有独立实现文件local.ts数据库队列默认、bullmq.tsRedis 队列、inngest.ts托管云服务统一由client.ts的JobClient聚合。任务清单对照 packages/lib/jobs/client.ts 的注册表邮件类任务definitions/emails/send-signing-email— 签署邀请send-confirmation-email— 邮箱验证send-recipient-signed-email— 有人签署后通知send-rejection-emails— 拒绝通知send-document-cancelled-emails— 取消通知以及send-document-completed-emails、send-document-pending-email、send-organisation-*系列、send-password-reset-success-email等内部任务definitions/internal/seal-document— 对已完成的文档加盖最终签名seal-document-sweep/expire-recipients-sweep/send-signing-reminders-sweep— 定时清扫型任务兜底补偿bulk-send-template— 批量发送模板execute-webhook— 对外 Webhook 调用sync-email-domains、sync-organisation-seats等值得注意的是任务定义在 packages/lib/jobs/client.ts 中通过as const断言注册进jobsClient源码注释明确说这保证了触发任务时的类型推断——即任务名和 payload 类型在编译期就可校验。六、可插拔 Provider用 ts-pattern 做策略选择这是 ARCHITECTURE.md 强调的核心模式四类外部能力都通过环境变量 ts-pattern的match分支选择具体实现业务代码只依赖抽象接口。6.1 存储 Provider负责文件上传/下载。Provider说明环境值Database文件以 Base64 存入库databaseS3S3 兼容存储可叠加 CloudFronts3配置NEXT_PUBLIC_UPLOAD_TRANSPORT.env.example 默认database位置packages/lib/universal/upload/源码层面packages/lib/universal/upload/put-file.server.ts 中读取NEXT_PUBLIC_UPLOAD_TRANSPORT后用match(...)分发到不同实现get-file.server.ts、delete-file.ts、update-file.ts等均遵循同一模式。NEXT_PUBLIC_前缀是因为前端也需要知道传输方式例如上传走直传 S3 还是走服务端中转。6.2 PDF 签署 Provider对文档做密码学签名。Provider说明环境值LocalP12 证书文件localGoogle Cloud HSMGoogle Cloud KMSgcloud-hsm配置NEXT_PRIVATE_SIGNING_TRANSPORT.env.example 默认local位置packages/signing/packages/signing/index.ts 是这一模式最典型的样本const transport env(NEXT_PRIVATE_SIGNING_TRANSPORT) || local; signer await match(transport) .with(local, async () await createLocalSigner()) .with(gcloud-hsm, async () await createGoogleCloudSigner()) .otherwise(() { throw new Error(Unsupported signing transport: ${transport}); });同一文件中的signPdf还展示了签署参数使用libpdf/core的pdf.sign()subFilter默认ETSI.CAdES.detached可由NEXT_PRIVATE_USE_LEGACY_SIGNING_SUBFILTER切回旧版adbe.pkcs7.detached并支持通过 TSA时间戳权威开启长期验证LTV与归档时间戳。6.3 邮件 Provider发送事务性邮件基于 Nodemailer 抽象出四种传输见 packages/email/mailer.tsProvider说明环境值SMTP Auth标准 SMTP 账号密码smtp-auth默认SMTP APISMTP API Keysmtp-apiResendResend APIresendMailChannelsMailChannels APImailchannels配置NEXT_PRIVATE_SMTP_TRANSPORT.env.example 默认smtp-auth位置packages/email/mailer.ts模板在 packages/email/templates/React Email约 30 个场景模板各传输所需的关键变量来自 mailer.ts 的 JSDoc 与实现mailchannelsNEXT_PRIVATE_MAILCHANNELS_API_KEY可选NEXT_PRIVATE_MAILCHANNELS_ENDPOINTresendNEXT_PRIVATE_RESEND_API_KEY缺失时直接抛错smtp-apiNEXT_PRIVATE_SMTP_HOSTNEXT_PRIVATE_SMTP_APIKEY用户名默认apikeysmtp-authNEXT_PRIVATE_SMTP_HOST默认127.0.0.1:2500即开发环境的 Inbucket、NEXT_PRIVATE_SMTP_PORT默认 587、NEXT_PRIVATE_SMTP_SECURE、NEXT_PRIVATE_SMTP_USERNAME/PASSWORD可选NEXT_PRIVATE_SMTP_SERVICE如gmail走简写服务名邮件模板渲染侧使用 React Email 组件packages/email/template-components/并支持团队自定义品牌template-branding-logo.tsx等。6.4 后台任务 ProviderProvider说明环境值Local数据库队列local默认BullMQRedis 队列bullmqInngest托管云服务inngest配置NEXT_PRIVATE_JOBS_PROVIDER.env.example 默认local位置packages/lib/jobs/client/七、请求流与文档签署流Web 请求处理链Browser │ ▼ Hono Server (apps/remix/server/) │ ├──▶ /api/v1/* ──▶ ts-rest handlers (packages/api/) │ ├──▶ /api/v2/* ──▶ tRPC OpenAPI handlers (packages/trpc/) │ ├──▶ /api/trpc/* ──▶ tRPC handlers (packages/trpc/) │ ├──▶ /api/jobs/* ──▶ Job handlers (packages/lib/jobs/) │ └──▶ /* ──▶ React Router (apps/remix/app/routes/) │ ▼ React Components (packages/ui/)实际挂载顺序含安全中间件可以在 router.ts 中核对先挂contextStorage()与应用上下文再挂安全响应头CSP 每请求 nonce、requestId 与结构化日志然后才是各 API 前缀的 CORS、限流与处理器。除四条 API 线外还有/api/auth认证、/api/files文件上传/下载、/api/aiAI 路由独立限流、/api/csc来自documenso/ee的证书服务 OAuth。文档签署端到端流程1. Upload Document ──▶ Storage Provider (DB/S3) │ 2. Add Recipients ────────────────┤ │ 3. Add Fields ────────────────────┤ │ 4. Send Document ─────────────────┤ │ │ ▼ │ Email Job ──▶ Email Provider | │ | 5. Recipient Signs ───────────────┤ │ │ ▼ │ seal-document Job | │ │ ▼ │ Signing Provider ◀─────────────┘ │ ▼ Signed PDF ──▶ Storage Provider这个流程把前文所有抽象串了起来上传走存储 Provider发出邀请触发邮件 Job签署完成后由seal-document任务调用签署 Provider 生成带密码学签名的 PDF再写回存储。seal-document-sweep清扫任务则保证主流程失败时仍有兜底。八、关键目录速查documenso/ ├── apps/ │ └── remix/ │ ├── app/ │ │ └── routes/ # React Router 路由 │ │ ├── _authenticated/ # 需登录路由 │ │ ├── _unauthenticated/ # 公开路由 │ │ └── _recipient/ # 签署方路由 │ └── server/ │ ├── router.ts # Hono 路由挂载 │ └── main.js # 入口 ├── packages/ │ ├── api/v1/ # API V1 (ts-rest) │ ├── trpc/server/ # API V2 内部 (tRPC) │ ├── lib/ │ │ ├── server-only/ # 服务端业务逻辑 │ │ ├── client-only/ # 客户端工具 │ │ ├── universal/ # 共享代码 │ │ └── jobs/ # 后台任务 │ ├── prisma/ # 数据库 schema 与 client │ ├── signing/ # PDF 签署 │ ├── email/ # 邮件模板 │ └── ui/ # 组件库 └── docker/ # Docker 配置其中packages/prisma/schema.prisma是全部数据模型的单一事实来源packages/prisma/migrations/ 下约 140 个迁移目录记录了从初始建表到 envelope多文档信封、组织、收件人过期、CSC 签名等级等历次演进。九、本地开发命令与 Docker 服务常用命令根目录执行# 一键完整初始化npm ci、docker 启动、prisma migrate、seed、dev npm run d # 仅启动开发服务器先编译 Lingui 翻译再跑 remix 的 dev npm run dev # 数据库 GUI npm run prisma:studio # 类型检查比 build 更快 npx tsc --noEmit # E2E 测试Playwright npm run test:e2e对照根 package.jsond实际展开为dxnpm cidocker compose -f docker/development/compose.yml up -dprisma:migrate-devprisma:seed 翻译编译 dev所有 prisma 脚本都通过dotenv -e .env -e .env.local注入环境变量。开发用 Docker 服务ARCHITECTURE.md 列出的三个服务之外当前 docker/development/compose.yml 还包含 Redis 与 Gotenberg文档转换完整对照如下服务端口说明PostgreSQL 1554320 → 5432主数据库用户/库名documensoInbucket9000Web、2500SMTP本地收信服务器即默认smtp-auth的127.0.0.1:2500MinIO9001Console、9002APIS3 兼容存储s3传输时使用Redis 863790 → 6379bullmq任务 Provider 使用Gotenberg3005 → 3000文档转换docx 等转 PDF启用 Basic Auth十、环境变量速查四大 Provider 的选择开关与 ARCHITECTURE.md 一致并已在.env.example中逐一核对默认值变量用途可选值默认值.env.exampleNEXT_PUBLIC_UPLOAD_TRANSPORT存储 Providerdatabase、s3databaseNEXT_PRIVATE_SIGNING_TRANSPORT签署 Providerlocal、gcloud-hsmlocalNEXT_PRIVATE_SMTP_TRANSPORT邮件 Providersmtp-auth、smtp-api、resend、mailchannelssmtp-authNEXT_PRIVATE_JOBS_PROVIDER任务 Providerlocal、bullmq、inngestlocal补充两条从 compose 文件可确认的配套约定NEXT_PRIVATE_REDIS_URL默认redis://localhost:63790映射到 compose 中的 Redis 6379 端口NEXT_PRIVATE_DOCUMENT_CONVERSION_URL默认http://localhost:3005与 Gotenberg 容器一一对应。完整配置项请查阅 .env.example约 250 行覆盖认证、SMTP、存储、签署、任务、文档转换等全部开关。小结Documenso 的架构可以用三条主线概括其一是单进程、多前缀——一个 Hono 应用同时挂载 v1 REST、v2 OpenAPI、内部 tRPC 与任务 API通过独立限流与认证隔离不同消费者其二是lib 分层 Provider 抽象——业务逻辑集中在documenso/lib存储/签署/邮件/任务四类外部依赖全部可插拔切换只需改环境变量其三是任务化异步——签署链路中凡是需要等待或可能失败的动作发邮件、盖章、Webhook都落为可重试的任务并配有 sweep 清扫任务兜底。理解了这三条线再对照本文列出的文件路径即可顺畅地深入任意子系统的源码。【免费下载链接】documensoThe Open Source DocuSign Alternative.项目地址: https://gitcode.com/GitHub_Trending/do/documenso创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考