1850 字
约 6 分钟
1
全栈开发的核心思维

全栈开发的核心思维

面向 VOZEB PRO。讲的不是「前后端都会写一点」,而是这个仓库强制你建立的一种看法:一个 Web 产品就是一个盒子,盒子里同时装着页面、HTTP、业务、SQL 和出站调用。


0. 什么时候用这套思维

适合:

  • ✅ 读这个仓库时,想知道为什么找不到独立的 backend/ 服务
  • ✅ 自己要加功能:登录、列表、生成任务、后台表单,不知道该改哪一层
  • ✅ 用 Next.js / Laravel / Rails / Django 这类「框架即应用服务器」的项目
  • ✅ 产品还在验证,需要一个人把一条链路从头走到尾

不适合硬套:

  • ⚠️ 已经按团队边界拆成多个可独立发布的服务
  • ⚠️ 只改 CSS / 只改 SQL,不需要理解整条请求链
  • ⚠️ 把「全栈」理解成「一个文件里既写 JSX 又写 SQL」——本项目恰恰禁止这么干

在 VOZEB PRO 中的定位: Next.js App Router 既是前端框架,也是应用服务器。浏览器、Route Handler、lib/serverpg、上游模型 HTTP,默认都住在同一个 web/ 进程里。这叫整体式全栈,不叫「没分层」。


1. 先纠正一个误会

「前后端不分离」说的是部署和进程,不是代码糊成一团

你可能以为的 这个仓库实际是
一个 page.tsx 里直接查库、调 AI 页面只渲染;请求走 services/api
没有后端 后端就是 app/api/**/route.ts + lib/server
全栈 = 不分层 分层很严,只是层与层在同一个进程、同一套 TypeScript 类型里
全栈 = 不需要 API 有 API,契约是 { code, data, msg },只是调用方和实现方在同一个仓库

官方请求链可以收成一句:

页面 → services/api → Route Handler → Session → lib/server → Repository → { code, data, msg }

全栈思维要练的,是顺着这条链看完整件事,而不是在某一层里当「纯前端」或「纯后端」。


2. 核心公式

整体式全栈 = 一个进程里跑完一次用户动作所需的全部服务端工作。

一次「点生成」在盒子里会发生:

  1. 浏览器里的工作台收集提示词、尺寸、参考图
  2. services/api 发 JSON 请求(密钥不在浏览器)
  3. Route 做鉴权、限流、入参映射,不写积分公式、不写 SQL
  4. lib/server 做领域校验、规划、建任务、扣积分
  5. Repository 用参数化 SQL 读写 PostgreSQL(或文件 Provider 回退)
  6. 需要上游模型时,同一进程用 undici 出站;渠道密钥只在服务端解密
  7. 长任务落成 generation_tasks 行,页面关掉后仍可由 Worker 续上

你要建立的直觉是:用户动作的权威状态在服务端,不在 Zustand,不在 localStorage。 客户端只持有主题、当前用户、公开配置这类瞬时状态。


3. 「一个盒子」到底共享了什么

同进程、同仓库带来的不是偷懒,是这些东西可以直接共用,而不用再开第二个服务:

  • 类型web/src/lib/*.ts 的领域契约前后端一起 import
  • 鉴权:用户 Cookie 和 Worker 维护令牌,都在 Route 进站时解析
  • 数据库连接与仓储:只有一套 database/,没有「前端库 / 后端库」两套模型
  • 任务恢复函数:页面 after() 和 Worker 调用的是同一个 runGenerationTaskRecoveryBatch
  • 部署单元web/ 打成 standalone 镜像;Worker 用同一镜像、另一条启动命令

换独立后端等于拆仓:鉴权 Cookie、SSE、standalone 启动、Worker 回调全要重接。这就是「换 Next 当纯前端」代价巨大的原因。


4. 全栈不等于越层

本项目用分层把「一个盒子」管住。读代码或写补丁时,先问自己在哪一层:

可以做 禁止做
页面 / hooks 展示服务端状态、收集输入 直连数据库、上游、对象存储
services/api 类型化 HTTP 把 API Key 塞进用户 Store
Route 鉴权、限流、调服务、映射响应 堆业务规则或 SQL
lib/server 校验、编排、计费、任务 runtime 为了省事绕过 Repository 手写散落 SQL
database/ 参数化查询、事务、定向过滤 先读整表再在 Node 里筛

三条全站不变量,改任何层都不能破:

  1. 计费键由服务端生成,浏览器的 Idempotency-Key 不能直接当流水号
  2. 媒体只存 storageKey,已有站内媒体必须复用
  3. 删除是聚合根事务,UI 写删除时服务端不得偷换成归档

能在一个进程里完成,不代表可以在一个文件里完成。


5. 对照本仓库,怎么练这个思维

拿一条真实路径走一遍,比背定义有用:

  1. 打开一个用户工作台页面(生图 / 视频 / Agent)
  2. 找到它调用的 web/src/services/api/*.ts
  3. 跳到对应 web/src/app/api/**/route.ts,只看它做了哪四件事:入参、鉴权、调服务、返回
  4. 再进 web/src/lib/server/,看规则和任务是在哪落地的
  5. 最后看 web/src/lib/server/database/ 的 SQL 条件:有没有按用户 / 实体 / 时间窗查询

走完你会得到一种手感:前端看到的失败文案,往往是服务端已经裁决过的 msg;前端看到的「生成中」,往往对应库里的一行任务,而不是某个 React state。

再对比旁路 Worker:generation-worker.mjs没有业务状态机,只有 fetch 打本站 /api/maintenance/...。这会逼你分清:

  • 同盒子 = 业务实现仍在 web 进程
  • 另一进程 = 只是多了一个会敲门的 HTTP 客户端

6. 写代码时的三问

  1. 这件事的权威结果应该写在哪? 库表 / 任务行 / 会话 Cookie,而不是组件 state。
  2. 我现在改的文件属于哪一层? 越层就停,把逻辑挪回该在的地方。
  3. 如果明天把页面删了,这个动作还能不能被 Worker 或另一个请求续上? 能,说明你把状态放对了;不能,说明你还在用内存当产品。

7. 读完能指挥自己(或 AI)做什么

  • 「给图片工作台加查询参数:只改页面 hook、services/api、Route 入参映射、config 校验。不要动表结构,除非参数要持久化。」
  • page.tsx 里出现 postgresQuery 或上游 Base URL,视为失败。」
  • 「先画这条请求穿过哪几层,再写补丁。」

相关文档

全栈开发的核心思维
http://www.clxhxhhr.top/posts/519/
作者
clxstart
发布于
2026-09-07
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。