架构总览
相关源文件
本页面内容基于以下源文件生成:
- back/index.ts
- back/router.ts
- src/admin/Main.tsx
- src/pc/Main.tsx
- src/shared/rpc.ts
- vite.config.ts
- back/init.ts
- back/migrate.ts
- back/index.d.ts
- back/rpc/base.ts
- back/procedures/index.ts
- Dockerfile
- package.json
- docker-compose.yml
- eslint.config.js
- src/pc/index.tsx
- back/models/base.ts
- src/admin/index.tsx
- src/admin/router.tsx
- back/procedures/test.ts
本项目是一个基于 TypeScript 的全栈 Web 应用,采用 Monorepo 架构组织代码。系统核心由 Fastify 后端服务与 Vite 驱动的多页前端应用构成,通过 TRPC 实现端到端类型安全的通信。数据持久化层使用 MongoDB 作为主存储,Redis 作为缓存与会话管理,整体架构设计注重开发效率与类型安全。
系统架构总览
系统采用经典的三层架构模式,前端层包含两个独立部署的 SPA 应用(Admin 管理后台与 PC 用户端),通过 TRPC 协议与后端 Fastify 服务通信。后端服务负责业务逻辑处理、数据持久化与定时任务调度。
正在加载图表渲染器...
架构要点说明:
-
多页应用隔离:Admin 与 PC 端作为独立 SPA 应用,拥有各自的入口文件与路由系统,通过 Vite 多页构建配置实现代码分离(vite.config.ts:31-38)
-
统一后端服务:Fastify 服务监听 5550 端口,集成 CORS、JWT、Redis、MongoDB 等中间件,提供 REST 与 RPC 双协议支持(back/init.ts:12-32)
-
类型安全通信:TRPC 框架提供端到端类型推导,前端通过共享
AppRouter类型定义获得完整的 IDE 智能提示与编译时类型检查(back/router.ts:19-21) -
数据库版本控制:内置迁移脚本系统,通过版本号标记管理数据库 Schema 变更,支持索引创建与数据结构演进(back/migrate.ts:4-31)
-
定时任务调度:基于 node-cron 实现后台任务调度,开发环境下每 10 秒执行健康检查,生产环境可配置业务定时任务(back/procedures/index.ts:4-11)
后端服务架构
后端服务基于 Fastify 框架构建,采用插件化架构组织功能模块。核心初始化流程在 back/init.ts 中完成,包括框架实例创建、中间件注册与外部服务连接。
框架初始化与插件配置
Fastify 实例创建时配置了路由参数长度限制(5000 字符),以支持复杂的查询参数场景。根据运行环境动态注册 CORS 中间件,开发环境允许所有来源的跨域请求(back/init.ts:19-23)。
typescript1// 核心插件注册顺序 21. @fastify/cors - 跨域资源共享(仅开发环境) 32. @fastify/jwt - JWT 认证(密钥:自定义秘钥) 43. @fastify/redis - Redis 连接(URL 来自环境变量) 54. @fastify/mongodb - MongoDB 连接(强制关闭模式)
JWT 插件使用硬编码密钥(生产环境应改为环境变量注入),Redis 与 MongoDB 连接均通过环境变量配置,支持容器化部署时的配置注入(back/init.ts:24-32)。
TRPC 路由定义与 OpenAPI 集成
TRPC 路由采用集中式注册模式,所有业务过程(Procedures)从 back/rpc/ 目录导入后聚合为单一路由对象。路由定义时通过 .meta() 方法附加 OpenAPI 元数据,包括接口摘要、描述与标签分类(back/router.ts:7-17)。
typescript1// 路由元数据自动转换逻辑 2for (const key of Object.keys(routes)) { 3 const route = routes[key] 4 if (route.meta?.openapi) { 5 api.method = route._def.type === "query" ? "GET" : "POST" 6 api.path = `/${key}` 7 } 8}
系统自动根据 TRPC 过程类型(query/mutation)映射 HTTP 方法,query 对应 GET 请求,mutation 对应 POST 请求。这种设计允许同一套代码同时支持 TRPC 调用与标准 REST API 访问(back/router.ts:10-16)。
服务启动与数据库迁移流程
后端服务启动流程采用顺序初始化策略:首先监听端口,然后执行数据库迁移,最后启动定时任务调度器。任何步骤失败都会导致进程退出(back/index.ts:96-104)。
typescript1async function main() { 2 const addr = await app.listen({ port: 5550, host: "0.0.0.0" }) 3 console.info(`服务器运行在内网: ${addr}`) 4 const v = await migrate() 5 console.info("数据库迁移完成,当前版本:v." + v) 6 await runProcedures() 7 console.info("定时任务启动完成") 8}
进程级别注册了 unhandledRejection 事件监听器,捕获所有未处理的 Promise 拒绝,防止静默失败(back/index.ts:106-108)。这种防御性编程策略在生产环境中至关重要,确保异常可追溯。
前端多页应用架构
前端采用 Vite 驱动的多页应用(MPA)架构,Admin 与 PC 端作为独立应用构建与部署。这种架构避免了单页应用随业务增长导致的包体积膨胀问题,同时允许不同端使用独立的路由与状态管理策略。
Vite 多页应用配置
Vite 配置文件定义了两个构建入口:admin.html 与 pc.html,分别对应管理后台与用户端应用。Rollup 构建选项确保输出文件按入口名称命名(vite.config.ts:31-38)。
开发服务器配置了自定义中间件,实现路由重写逻辑:访问 /admin/* 路径时重写为 /admin.html,访问 /pc/* 路径时重写为 /pc.html。这种 SPA fallback 策略确保前端路由正常工作(vite.config.ts:14-27)。
typescript1// 开发服务器路由重写中间件 2configureServer(server) { 3 server.middlewares.use((req, _res, next) => { 4 if (req.url) { 5 if (url.startsWith("/admin")) req.url = "/admin.html" 6 else if (url.startsWith("/pc")) req.url = "/pc.html" 7 } 8 next() 9 }) 10}
构建配置还集成了 React Compiler(Babel 插件)与 Tailwind CSS Vite 插件,实现组件级自动优化与原子化 CSS(vite.config.ts:7-13)。
Admin 与 PC 端独立入口
两个前端应用拥有平行的入口结构,均使用 React 18 的 createRoot API 挂载组件。入口文件配置了 Ant Design 的中文语言包与全局主题,通过 ConfigProvider 组件注入(src/admin/index.tsx:16-28)。
Admin 端入口注册了全局 TRPC 错误监听器,通过 subscribeRpcError 函数订阅 unhandledrejection 事件,过滤出 TRPC 错误并使用 Ant Design 的 message.error 显示(src/admin/index.tsx:13-15)。
typescript1// 全局错误处理订阅 2subscribeRpcError((e) => { 3 message.error(e) 4})
PC 端入口采用相同的错误处理策略,两个应用共享 src/shared/rpc.ts 中的客户端配置与错误处理逻辑(src/pc/index.tsx:13-15)。
React 组件挂载与路由配置
Admin 端使用 react-router-dom 的 RouterProvider 组件挂载路由配置,路由定义独立于入口文件,便于后续扩展菜单生成功能(src/admin/router.tsx:1-18)。
typescript1// 路由配置示例 2export const routes = [ 3 { 4 path: "/", 5 element: <Dashboard />, 6 icon: <HomeOutlined />, 7 label: "看板", 8 }, 9]
路由配置对象扩展了 RouteObject 类型,添加 icon 与 label 字段,用于自动生成侧边栏菜单。路由实例创建时指定 basename: "/admin",确保与开发服务器的路由重写规则匹配(src/admin/router.tsx:16-18)。
前后端通信机制
系统采用 TRPC 框架实现前后端通信,提供端到端类型安全。TRPC 允许前端直接调用后端函数(语义上),底层自动处理 HTTP 请求序列化与响应解析。
TRPC Client 配置
前端 RPC 客户端在 src/shared/rpc.ts 中创建,使用 @trpc/client 的 createTRPCClient 函数。客户端配置了 HTTP 批处理链接,根据环境变量动态选择后端地址(src/shared/rpc.ts:12-18)。
typescript1export default createTRPCClient<AppRouter>({ 2 links: [ 3 httpBatchLink({ 4 url: import.meta.env.PROD ? "/api/rpc" : "http://localhost:5550/rpc", 5 }), 6 ], 7})
开发环境下客户端直连后端 5550 端口,生产环境下使用相对路径 /api/rpc,依赖反向代理转发请求。这种配置避免了开发环境的 CORS 问题,同时保持生产部署的灵活性(src/shared/rpc.ts:14-16)。
类型安全的 RPC 调用
前端组件通过导入共享的 RPC 客户端实例调用后端接口。Admin 端 Main 组件在 useEffect 钩子中调用 rpc.ping.mutate() 方法,该方法对应后端的 ping mutation 过程(src/admin/Main.tsx:6-11)。
typescript1useEffect(() => { 2 rpc.ping.mutate().then((res) => { 3 console.log(res) // res 类型自动推导为 "pong" 4 }) 5}, [])
TypeScript 编译器会验证调用参数与返回值类型,任何类型不匹配都会在编译时报错。这种类型安全机制消除了前后端接口契约不一致的风险(src/admin/Main.tsx:8-10)。
统一错误处理
RPC 客户端导出了 subscribeRpcError 函数,用于订阅全局 TRPC 错误。该函数监听浏览器的 unhandledrejection 事件,通过错误对象的 name 属性过滤出 TRPCClientError(src/shared/rpc.ts:4-10)。
typescript1export function subscribeRpcError(cb: (err: string) => void) { 2 window.addEventListener("unhandledrejection", (e) => { 3 if (e.reason?.name === "TRPCClientError") { 4 cb(e.reason.message) 5 } 6 }) 7}
这种设计允许应用在入口处统一处理所有未捕获的 RPC 错误,避免在每个组件中重复编写错误处理逻辑。错误消息通过回调函数传递,调用方可选择使用 Ant Design 的 message.error 或其他通知机制展示(src/shared/rpc.ts:5-9)。
数据模型与存储
数据持久化层使用 MongoDB 作为主存储,采用 Zod 库定义 Schema 并提供运行时类型校验。数据模型定义与集合访问函数集中在 back/models/base.ts 中管理。
MongoDB 集合定义
系统通过 Fastify MongoDB 插件获取数据库实例,使用工厂函数模式创建集合访问器。每个集合访问器函数返回指定类型的 MongoDB Collection 对象(back/models/base.ts:32-44)。
typescript1export const pingHisCols = () => 2 app.mongo.db!.collection<PingHis>("pingHis") 3 4export const dbVersionCols = () => 5 app.mongo.db!.collection<DbVersion>("dbVersions")
这种设计避免了在模块加载时访问数据库实例(此时 Fastify 尚未完成初始化),同时提供类型安全的集合操作。泛型参数确保 find、insertOne 等方法的参数与返回值类型正确(back/models/base.ts:32-33)。
Zod Schema 定义
数据模型使用 Zod 库定义,同时提供运行时校验与 TypeScript 类型推导。pingHis Schema 定义了三个字段:时间戳、IP 地址与请求次数(back/models/base.ts:36-40)。
typescript1export const pingHis = z.object({ 2 time: z.number().describe("时间戳"), 3 ip: z.string().describe("请求IP地址"), 4 count: z.number().describe("请求次数"), 5}) 6 7export type PingHis = z.infer<typeof pingHis>
z.infer 工具类型从 Schema 自动推导 TypeScript 类型,确保类型定义与 Schema 同步。describe 方法附加的字段描述会自动同步到 OpenAPI 文档(back/models/base.ts:42)。
系统还定义了通用的高阶 Schema 工厂函数,如 paginatedResult 用于生成分页响应结构(back/models/base.ts:17-22)。
数据库迁移脚本
迁移系统通过版本号标记管理数据库结构演进。migrate 函数首先查询当前版本,然后按顺序执行各版本的迁移逻辑(back/migrate.ts:4-31)。
typescript1export default async function migrate() { 2 const version = await dbVersionCols().findOne({ tag: "base" }) 3 let v = version ? version.version : 0 4 5 if (!version) { 6 // 初始化版本记录 7 await dbVersionCols().updateOne( 8 { tag: "base" }, 9 { $set: { version: 0 } }, 10 { upsert: true } 11 ) 12 } 13 14 if (v === 0) { 15 await pingHisCols().createIndex({ time: -1 }) 16 await dbVersionCols().updateOne({ tag: "base" }, { $set: { version: 1 } }) 17 v++ 18 } 19 // 后续版本迁移... 20}
每个版本迁移块包含索引创建、集合重命名、数据转换等操作。迁移完成后更新版本号,确保下次启动时跳过已执行的迁移。这种幂等设计支持安全回滚与重新部署(back/migrate.ts:25-31)。
环境变量类型定义在 back/index.d.ts 中声明,包含 MongoDB 连接 URI、数据库名称、Redis URI 等配置项(back/index.d.ts:3-13)。
定时任务与后台服务
后台任务调度基于 node-cron 库实现,支持标准的 cron 表达式配置。任务定义与调度器启动逻辑集中在 back/procedures/ 目录。
Cron 定时任务调度
runProcedures 函数负责启动所有定时任务。开发环境下注册了一个每 10 秒执行一次的健康检查任务,生产环境可根据业务需求配置实际任务(back/procedures/index.ts:4-11)。
typescript1export default function runProcedures() { 2 if (process.env.NODE_ENV === "development") { 3 cron.schedule("*/10 * * * * *", testService, { 4 timezone: "Asia/Shanghai", 5 }) 6 } 7}
Cron 表达式 */10 * * * * * 表示每分钟的第 0、10、20、30、40、50 秒执行。时区配置为 Asia/Shanghai,确保任务在正确的本地时间执行(back/procedures/index.ts:7-9)。
后台服务逻辑
示例任务 testService 查询数据库版本并打印日志,用于验证服务运行状态。实际业务中可替换为数据清理、报表生成、消息推送等任务(back/procedures/test.ts:3-8)。
typescript1export default async function testService() { 2 const v = await dbVersionCols().findOne({ tag: "base" }) 3 console.log(`v.${v?.version || 0}版本服务器运行正常`) 4}
任务函数使用 async/await 语法处理异步操作,未捕获的异常会被进程级别的 unhandledRejection 监听器捕获。生产环境建议添加任务重试与告警机制(back/procedures/test.ts:4-7)。
核心数据流与调用链
以下时序图展示了前端组件调用后端 TRPC 过程的完整数据流,包括请求发起、服务端处理与响应返回。
正在加载图表渲染器...
数据流要点说明:
-
请求发起:React 组件通过
useEffect钩子调用 RPC 客户端的mutate()方法,触发异步请求(src/admin/Main.tsx:8) -
请求序列化:TRPC 客户端将函数调用转换为 HTTP POST 请求,请求体包含过程名称与参数的 JSON 序列化(src/shared/rpc.ts:14-16)
-
服务端处理:Fastify 接收请求后,TRPC 中间件根据路径
/rpc/ping路由到对应的 procedure 处理器(back/router.ts:19) -
数据库操作:
ping过程执行 MongoDB 的updateOne操作,使用 upsert 模式确保文档存在(back/rpc/base.ts:21-34) -
响应返回:服务端返回字面量
"pong",TRPC 自动校验输出类型匹配 Zod Schema 定义(back/rpc/base.ts:35)
核心设计决策与取舍
| 决策点 | 选择方案 | 理由 | 已知限制 |
|---|---|---|---|
| 前端架构 | Vite MPA | 避免单包体积膨胀,支持独立部署 | 共享代码需手动提取 |
| 通信协议 | TRPC | 端到端类型安全,无需 Schema 定义文件 | 与非 TS 客户端集成困难 |
| 数据库 | MongoDB | 灵活 Schema,适合快速迭代 | 缺少强一致性事务 |
| 缓存层 | Redis | 高性能会话存储,支持发布订阅 | 增加运维复杂度 |
| 样式方案 | Tailwind CSS | 原子化 CSS,开发效率高 | 类名较长,HTML 体积增加 |
| 定时任务 | node-cron | 简单易用,无需额外依赖 | 不支持分布式锁 |
| 构建工具 | Vite | 快速 HMR,原生 ESM 支持 | 部分老旧插件不兼容 |
| ORM 策略 | 原生 MongoDB Driver | 性能最优,无抽象层开销 | 需手动处理关联查询 |
关键架构权衡:
-
TRPC vs GraphQL:选择 TRPC 放弃了 GraphQL 的灵活查询能力,但获得了更简单的开发模型与更小的运行时开销。对于内部管理系统,TRPC 的类型安全优势更为重要。
-
MPA vs SPA:多页应用架构牺牲了页面间状态共享的便利性,换取了更好的构建隔离与独立部署能力。Admin 与 PC 端业务差异较大时,这种架构更为合适。
-
Zod 运行时校验:在 TRPC 过程中使用 Zod 进行输入输出校验,虽然增加了少量运行时开销,但提供了强类型保障与自动生成的 API 文档。
-
硬编码 JWT 密钥:当前实现使用硬编码密钥(back/init.ts:24),生产环境必须改为环境变量注入,否则存在安全风险。
-
开发环境 CORS 全开:开发环境配置
origin: "*"(back/init.ts:21),便于本地调试,但生产环境必须配置严格的白名单。
技术选型详表
| 技术 | 版本要求 | 用途 | 选型理由 | 替代方案 |
|---|---|---|---|---|
| Fastify | ^4.x | 后端框架 | 高性能,插件生态丰富 | Express, Koa, NestJS |
| TRPC | ^10.x | RPC 框架 | 端到端类型安全 | GraphQL, tRPC, gRPC |
| React | ^18.x | 前端框架 | 组件化,生态成熟 | Vue, Svelte, Angular |
| Vite | ^5.x | 构建工具 | 快速 HMR,原生 ESM | Webpack, Rollup, esbuild |
| MongoDB | ^6.x | 主数据库 | 灵活 Schema,水平扩展 | PostgreSQL, MySQL |
| Redis | ^7.x | 缓存/会话 | 高性能,支持多种数据结构 | Memcached, etcd |
| Zod | ^3.x | Schema 校验 | TypeScript 优先,类型推导 | Joi, Yup, io-ts |
| Ant Design | ^5.x | UI 组件库 | 企业级组件,中文支持好 | Material UI, Chakra UI |
| node-cron | ^3.x | 定时任务 | 标准 cron 语法,轻量 | Agenda, Bull, node-schedule |
| dayjs | ^1.x | 日期处理 | 轻量,API 兼容 Moment | date-fns, luxon |
| React Router | ^6.x | 前端路由 | 声明式,支持懒加载 | TanStack Router, Wouter |
模块依赖关系
正在加载图表渲染器...
依赖关系说明:
-
前端共享层:
shared/rpc.ts被 Admin 与 PC 端共同依赖,封装了 TRPC 客户端配置与错误处理逻辑(src/shared/rpc.ts:12-18) -
后端核心层:
back/init.ts提供框架实例与 TRPC 构建器,被路由层与过程层依赖(back/init.ts:34-36) -
数据访问层:
back/models/base.ts被迁移脚本、RPC 过程、定时任务共同依赖,提供统一的集合访问接口(back/models/base.ts:32-44) -
启动流程:
back/index.ts作为入口文件,协调初始化、迁移、路由注册与任务启动的顺序(back/index.ts:96-104)
关键配置与启动流程
环境变量配置
系统依赖以下环境变量运行,定义在 back/index.d.ts 中:
| 变量名 | 类型 | 必需 | 说明 |
|---|---|---|---|
| NODE_ENV | string | 是 | 运行环境:development/production |
| MONGO_URI | string | 是 | MongoDB 连接字符串 |
| MONGO_NAME | string | 是 | 数据库名称 |
| REDIS_URI | string | 是 | Redis 连接字符串 |
| DEFAULT_USER_ID | string | 是 | 默认用户标识 |
环境变量类型声明确保 TypeScript 编译器能正确推导 process.env 属性类型(back/index.d.ts:3-9)。
启动流程详解
正在加载图表渲染器...
启动阶段说明:
-
端口监听:Fastify 服务绑定
0.0.0.0:5550,允许容器内外访问(back/index.ts:97) -
数据库迁移:查询版本记录,按需执行增量迁移脚本(back/migrate.ts:4-31)
-
任务调度:根据环境注册定时任务,开发环境启动健康检查(back/procedures/index.ts:4-11)
-
异常捕获:进程级别注册
unhandledRejection监听器,确保异常可追溯(back/index.ts:106-108)
开发与生产配置差异
| 配置项 | 开发环境 | 生产环境 |
|---|---|---|
| 后端地址 | localhost:5550 | /api/rpc(代理) |
| CORS | origin: "*" | 需配置白名单 |
| 定时任务 | 每 10 秒健康检查 | 按需配置业务任务 |
| 日志级别 | logger: false | 建议开启 |
| JWT 密钥 | 硬编码 | 必须使用环境变量 |
