AI 已经能写出大多数后端样板代码。但真正的工程能力不在于怎么写,而在于为什么这样设计——数据该怎么建模,接口该如何定义,崩溃时数据会是什么状态。这些判断力,AI 无法替你建立。
本文以一个多用户笔记应用为载体,带你建立后端工程师最核心的五块思维。
数据建模
后端开发最重要的判断发生在第一步——决定数据的形状。给你三个实体:用户、文件夹、笔记,该怎么建表?
关系设计
用户和笔记、文件夹都是一对多关系,一个用户拥有多个文件夹和笔记。笔记和文件夹的关系取决于一个业务决策:一篇笔记能同时出现在多个文件夹吗?
工程师的克制原则:不要为了"未来可能"过度设计。先做一对多,真的需要多对多再迁移。过早的灵活性是复杂性的来源。
协作编辑是多用户访问同一篇笔记,本质是多对多——但不要给 Note 表加字段,而是独立建一张权限表:
| 表 | 核心字段 | 设计要点 |
|---|---|---|
user | id, email, password_hash | 密码存 hash,绝不明文 |
folder | id, name, user_id | user_id 外键,一对多 |
note | id, title, content, user_id, folder_id | folder_id 可为 NULL,笔记不强制在文件夹里 |
user_note_access | user_id, note_id, role | 权限与业务数据分离,RBAC 模式 |
数据建模的判断力 = 克制。不是能想到多少种设计,而是知道什么时候不要过度设计,以及当需要扩展时,结构能不能平滑演进。
API 设计
前端是 API 的消费者,后端是设计者。设计一个笔记列表接口,看似简单,实则有很多值得思考的细节。
成功响应(部分字段示意):
{
"data": [
{
"id": "uuid",
"title": "我的第一篇笔记",
"folder_id": "uuid | null",
"created_at": "2024-01-01T00:00:00Z"
// ⚠ 不返回 content,列表页不需要正文
}
],
"pagination": {
"page": 1,
"size": 20,
"total": 142,
"has_more": true
}
}
两个最容易踩的坑
列表不要返回 content 正文。 列表页只展示标题,却把每篇笔记的几千字正文都传回来,流量和解析开销会放大几十倍。字段按需返回,详情页才拿 content。
错误格式要统一。 任何错误都应该返回同一个形状,前端才能写通用拦截器:
{
"error": {
"code": "UNAUTHORIZED", // 机器可读
"message": "请先登录" // 人类可读
}
}
认证机制
前端知道”带 token 请求,后端验证”,但后端工程师需要理解这个 token 从哪来、长什么样、为什么能验证。
JWT 的天然缺陷:无法主动吊销
用户修改密码后,旧 token 在过期之前仍然有效。攻击者的 token 最多还能再用几小时。这是 JWT 的设计缺陷,三种常见应对方案:
| 方案 | 原理 | 优点 | 适用场景 |
|---|---|---|---|
| 短过期时间 | Access Token 15min,Refresh Token 7天 | 无需改架构,纯无状态 | 大多数场景首选 |
| Token 黑名单 | 吊销时将 token ID 写入 Redis | 可立即失效,最安全 | 金融、医疗等高安全场景 |
| 密码版本号 | User 表加 password_version,JWT 带上,每次改密 +1 | 实现简单,改密即失效 | 中小型项目折中方案 |
JWT 本质上是"无法主动撤回的授权书",过期时间是唯一的内置失效机制。任何需要主动吊销的场景,都要在无状态和安全性之间做权衡——没有完美答案,只有适合业务的答案。
数据库索引
接口越来越慢,第一个怀疑的不是并发,而是 SQL 本身。最常见的元凶:没有索引,数据库做了全表扫描。
加索引只需要一行 SQL,但索引不是越多越好:
-- 给 folder_id 加索引
CREATE INDEX idx_note_folder_id ON note(folder_id);
| 读(SELECT) | 写(INSERT/UPDATE) | |
|---|---|---|
| 无索引 | 慢(全表扫描) | 快 |
| 有索引 | 快(直接定位) | 慢一点(同时维护索引) |
索引应该加在查询频繁、写入不那么频繁的字段上。排查慢接口的顺序:先看 SQL,再看索引,并发是最后才考虑的。
事务
删除一个有 10 篇笔记的文件夹:先删笔记,再删文件夹。如果删完 5 篇后服务器崩溃,数据库里会是什么状态?
不是数据丢失,而是数据不一致——文件夹还在,但里面只剩 5 篇笔记,另外 5 篇凭空消失,数据永久损坏。事务就是为了解决这个问题。
代码层面非常简单:
// ❌ 无事务 — 崩溃后数据不一致
await db.query('DELETE FROM note WHERE folder_id = ?', [folderId])
await db.query('DELETE FROM folder WHERE id = ?', [folderId])
// ✅ 有事务 — 崩溃自动回滚
await db.transaction(async (trx) => {
await trx('note').where({ folder_id: folderId }).delete()
await trx('folder').where({ id: folderId }).delete()
// 两步都成功才 commit,任何一步报错自动 rollback
})
判断标准:如果你的操作需要改两张表以上,或同一张表改多行,且这些操作必须同时成功,就必须用事务。
结语:AI 时代的工程判断力
AI 可以替你写出正确的 SQL,但不能替你决定 folder_id 该不该可为空;AI 可以生成 JWT 验证中间件,但不能替你权衡 token 吊销方案;AI 会写事务代码,但不知道你的业务里哪些操作需要被包裹在事务里。
你需要学的不是更少的编程,而是更高层次的编程思维。写代码的动作可以交给 AI,但理解代码、判断代码、设计系统的能力反而变得更值钱——因为这正是 AI 目前做不好的部分。