Skip to content

后端思维补齐

前端视角和后端视角的差异

前端更常关注:

  • 页面状态如何展示。
  • 交互是否顺滑。
  • 接口字段是否够用。
  • 组件如何复用。
  • 用户操作如何被反馈。

后端更常关注:

  • 数据是否能长期保持正确。
  • 多个请求同时进来是否会冲突。
  • 哪些字段必须唯一。
  • 哪些操作必须放在事务里。
  • 哪些状态允许流转,哪些不允许。
  • 失败后能否重试,重试会不会产生重复数据。

这不是谁更高级,而是职责不同。你转后端时,最关键的一步是从“页面字段”切到“业务状态”。

表不是页面表单

错误思路:

txt
页面上有用户、角色、权限三个输入框
→ 建一张 user 表,字段是 name、role、permission

更好的思路:

txt
用户是否独立存在?
角色是否独立维护?
权限是否会被多个角色复用?
一个用户是否能有多个角色?
一个角色是否能有多个权限?

这样你会自然拆出:

txt
users
roles
permissions
user_roles
role_permissions

前端页面是某个时刻的视图,数据库表是业务长期存在的结构。

接口不是函数入口

前端调用接口时,容易把接口理解成“远程函数”:

txt
click button → call API → get result

后端要多想几步:

txt
这个 API 是否会被重复调用?
这个 API 修改了哪些表?
这些修改是否必须同时成功?
是否有权限限制?
失败后能不能重试?
重试是否会重复创建数据?
多个用户同时调用是否会互相覆盖?

例如“发布文章”不是简单的:

python
article.status = "published"

而是状态流转:

txt
draft → reviewing → published
rejected → draft
published 不允许直接回到 reviewing

这类规则应该由后端守住,不能只靠前端按钮隐藏。

后端设计的五个固定问题

每次拿到一个需求,都可以按这五个问题拆:

1. 实体是什么

实体是有独立生命周期的业务对象:

  • 用户
  • 订单
  • 地址
  • 文档
  • 报表任务
  • 知识库
  • Agent 会话

判断标准:

txt
它是否需要独立新增、修改、删除、查询?
它是否需要自己的 ID?
它是否会被其他对象引用?
它是否需要保存历史?

2. 关系是什么

常见关系:

txt
一对一:user - user_profile
一对多:user - address
多对多:user - role

前端可能只看到一个“多选框”,但后端要把多选关系落到中间表。

3. 约束是什么

约束是数据库帮你守住底线:

txt
手机号唯一
订单号唯一
库存不能小于 0
状态不能为空
同一个用户不能重复收藏同一篇文章

能用数据库唯一索引守住的,不要只靠代码 if exists

4. 状态如何流转

只要有 status 字段,就要问:

txt
有哪些状态?
初始状态是什么?
哪些状态能互相转换?
谁能触发转换?
是否要记录转换时间和操作者?

例如任务:

txt
pending → running → success
pending → running → failed
failed → pending

5. 并发会不会冲突

只要接口会改数据,就要问:

txt
重复提交会怎样?
两个用户同时操作会怎样?
数据库更新一半失败会怎样?
消息重试会怎样?

这一步就是你从“写接口”进入“写可靠后端”的分界线。

一个小例子:RAG 文档上传

需求:

用户上传 PDF 到知识库,系统异步解析、切块、向量化,最后可检索。

前端看到的是:

txt
上传文件
显示进度
展示成功/失败

后端要拆成:

txt
knowledge_bases       知识库
documents             文档元信息
document_parse_jobs   解析任务
document_chunks       切块结果

还要考虑:

txt
同一个文件重复上传怎么办?
解析失败能不能重试?
任务是否会被多个 worker 同时执行?
删除文档时 chunk 怎么处理?
向量库写入失败后数据库状态怎么办?

这就是后端思维:不是把上传接口写完,而是把整个业务状态生命周期设计清楚。


RESTful API 设计规范

URL 命名

txt
✅ POST   /api/v1/users              # 创建用户
✅ GET    /api/v1/users              # 查询用户列表
✅ GET    /api/v1/users/{id}         # 查询单个用户
✅ PATCH  /api/v1/users/{id}         # 更新用户部分字段
✅ DELETE /api/v1/users/{id}         # 删除用户
✅ GET    /api/v1/users/{id}/orders  # 用户的订单列表

❌ /api/getUsers
❌ /api/createUser
❌ /api/userList
❌ /api/deleteUser?id=1

关键规范:

  • 用名词复数,不要用动词
  • 版本号放在 URL 路径里(/api/v1/
  • 层级关系用路径表达(/users/{id}/orders
  • 操作通过 HTTP 方法表达,不在 URL 上写动作

Pagination

txt
# 请求
GET /api/v1/users?page=1&page_size=20

# 响应
{
  "data": [...],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total": 156,
    "total_pages": 8
  }
}

统一错误响应

json
{
  "error": {
    "code": "USER_NOT_FOUND",
    "message": "用户不存在",
    "details": null
  },
  "request_id": "req_abc123"
}

HTTP 状态码速查

txt
200  查询或更新成功
201  创建成功
204  删除成功(无响应体)
400  请求参数错误
401  未认证
403  无权限
404  资源不存在
409  冲突(重复创建、状态冲突)
422  校验失败
429  请求过多
500  服务端内部错误

单体 vs 微服务

txt
单体应用:所有功能在一个项目里
  → 优点:开发简单、部署简单、调试方便
  → 缺点:项目变大后难以维护、无法独立扩展

微服务:按业务拆分成多个独立服务
  → 优点:独立部署、独立扩展、技术栈灵活
  → 缺点:运维复杂、网络开销大、数据一致性难

前端转后端应该怎么选:

  • 初期不要微服务,单体足够
  • 项目明显变大后再按业务边界拆分
  • 微服务要解决的问题是"团队规模和系统复杂度",不是"技术好不好看"

架构设计五原则

1. 解耦原则

txt
"读写分离"的本质不是加一个从库,而是把读和写的职责拆开。
任何"把一个大系统切成几个小系统"的动作,本质都是在解耦。

2. 无状态原则

txt
应用实例不保存用户状态(Session 集中存 Redis / Token 自包含)。
这样任何实例都能处理任何请求,才能水平扩展。

3. 兜底原则

txt
任何外部依赖都可能挂:数据库、缓存、MQ、第三方 API。
调用外部依赖时,永远做好"它挂了怎么办"的预案。

4. 幂等原则

txt
同一个操作执行一次和重复执行多次的效果相同。
创建资源用唯一约束、扣库存用条件更新、MQ 消费用状态机。

5. 可观测原则

txt
上线前就想好:怎么查日志、怎么监控、怎么告警。
不只在本地调试时才加日志,线上问题定位全靠它们。

面试怎么说

做后端设计时,我习惯先拆实体和关系、定约束、画状态流转、想并发冲突。API 设计遵循 REST 规范,URL 用名词复数、操作通过 HTTP 方法表达、统一分页和错误响应。系统设计优先单体,拆微服务时按业务边界划分。五个原则贯穿始终:解耦、无状态、兜底、幂等、可观测。